diff --git a/source/components/deployment.html.md b/source/components/deployment.html.md index 5f13cab..4e19b51 100644 --- a/source/components/deployment.html.md +++ b/source/components/deployment.html.md @@ -304,9 +304,9 @@ non-realtime and with the lowest priority possible. Changing it is done with the following statements: ~~~ ruby - task('TaskName', 'orogen_project::TaskClass'). - realtime. - priority() + task('TaskName', 'orogen_project::TaskClass') + .realtime + .priority() ~~~ Where the priority value is a number between 1 (lowest) and 99 (highest). @@ -319,8 +319,8 @@ a chain of method calls, and require the dots. The second case is called a sequential activity and is declared with: ~~~ ruby - task('TaskName', 'orogen_project::TaskClass'). - sequential + task('TaskName', 'orogen_project::TaskClass') + .sequential ~~~ **Next** this is mostly all. [The next page](runtime.html) describes diff --git a/source/components/index.html.md b/source/components/index.html.md index 302e3e8..4cdac9e 100644 --- a/source/components/index.html.md +++ b/source/components/index.html.md @@ -1,17 +1,161 @@ --- layout: documentation -title: Introduction +title: Writing Components sort_info: 0 directory_title: Components directory_sort_info: 40 --- -# Components +# Writing Components {:.no_toc} - TOC {:toc} +Within Rock, components are _implemented_ in C++. They are also _specified_ in a +Ruby domain-specific language that is processed by a code generation tool, +**oroGen**. This tool ensures that the component's interface matches its +specification. It also removes most of the crude boilerplate-writing code that +is the declaration in C++ of the component interfaces. + +From a package point of view, components are defined in an orogen package. The +orogen packages are all placed in the `/orogen/` subdirectory of one of the +[package categories](../workspace/conventions.html). This page will not dwell +on how to create an orogen package. [Check this page out](./orogen_packages.html) +for more details on the subject. + +**Important** an oroGen package and a library can share the same basename (e.g. +`drivers/hokuyo` and `drivers/orogen/hokuyo`). This is even a recommended +behavior when an orogen package is mainly tied to a certain library. +{: .note} + +Within oroGen packages, the components are named _Task Contexts_. This is +historically the name chosen by the developers of the Orocos RTT toolkit, the +underlying C++ component implementation used by Rock and orogen. + +A component is like a black box that you configure, start, feed data to (write +data) to get some data out (read data). This page will give you an overview of how +to create your own, along with the most important guidelines on how to write a +"good" component. + +To start building a component, you have to _define its interface_, answering +four questions: + +1. what is its name ? +2. what data will it need as input ? (_input ports_) +3. what data will it need as output ? (_output ports_) +4. what data will it need as configuration ? (_properties_) + +Once the interface is defined, you can use orogen to generate a C++ scaffold, and +fill the scaffold to actually implement the component. + +In summary, the overall development process looks like this: + +![Runtime Workflow Diagram](media/deployment_process.svg){: .fullwidth} + +1. components are implemented and unit-tested in orogen projects (design and + implementation) +2. you choose how the components will actually run in your system (deployment) +3. the component is being executed (by Syskit) + +## Designing the Interface + +The component interfaces are written in the package's `.orogen` file. This section +will first present the role of the major parts of a component interface, to finish +with pointers to other pages for details on how to actually write the component's interface definition. + +### The component name + +This is the one thing that is painful to change. Changing data types, adding and +removing inputs, outputs or properties is painless. The easiest way to change a +component's name is to copy all the component implementation from one class to +another. + +**Guidelines** + +- suffix all component names by `Task` +- if the oroGen package you are writing should not have more than one component, + simply use `Task` +- avoid duplicating the namespace. Almost every place where you will refer to the + component name will require you to specify the namespace, so it makes names + only longer. For instance, do not name `camera_gigevision::GigEVisionCameraTask`, but + simply `camera_gigevision::Task` + +### Note about data types + +Provided you followed a few rules, oroGen is meant to be using the C++ types you +are using in your libraries directly. This is by far the recommended workflow, +as your components will follow the progress on the library implementation naturally, +without adding to the library code a dependency on orogen-generated code. + +### Input and Output Ports + +The list of input and output ports is obviously very system-specific. While port names +are relatively easy to change, port _types_ are a rather critical choice. + +It is nonetheless generally speaking a very good idea to replicate the naming +scheme used by other components in your system. We only recommend a specific +naming scheme for ports that [generate transformations](geometric_transformations.html). + +**Reusing the types is critical**. For an input to be connected to an +output, they need to share the same type. This is the reason why "superset" types +such as for instance Rock's base/samples/RigidBodyState exist. They are here to allow +using a full state estimate with linear and angular position and velocities to be used +for e.g. a simple position. + +Also, for this reason, changing a port's type is close to impossible in practice, since +it requires changing the type in all the system(s) the component is being used. What +is done instead is creating another port with the updated type and change step by step, +which is rather effort-consuming (and therefore, avoided in practice) + +What has just been said obviously applies only to ports that are meant to be +connected to another component in the system. As we will see, it is a very good +practice to have "status" or "debug" ports in your components. These ports will +only ever be connected to a logger, and for those we usually define a data +structure that is specific to the component being developed. +{: .note} + +**Guidelines** + +- **group together data that belong together**. Do not create 5 ports to get a piece of + data because, for instance, you could not find an existing data structure for that + combination. Re-combining data that belong together is difficult in an asynchronous + system like Rock. +- **port names need to be unique**, an input cannot be named the same as an output + +### Properties + +The property interface has a lot less constraints than the ports, especially when +it comes to types. It is really only a contract between Syskit and the +component, that does not involve other components in the system. However, it is +generally speaking good to follow the same naming scheme than other components +of the same kind in your system, for simplicity of development. As for ports, +it is good to group properties that really belong together in structures, to +avoid creating massive unstructured configuration interfaces. + +oroGen allows you to define default parameters for simple types in the specification +directly, and allows you to initialize the more complex types in the C++ code. Always +do so. Always go for "safe" values as defaults, abstracting yourself from the system +you are currently developing as much as possible. + +### Actually Writing and Updating the Interface Description + +Now is a good time to go read on: + +- [how to define types](./defining_types.html) +- [how to import types](./importing_types.html) +- [how to declare a component interface](./interface.html) + +Once you have created / updated a `task_context` block, run `amake` to do +code generation and verify that everything is fine. Code generation will create +a C++ class for you to modify in `tasks/`, matching exactly the task context name. + +**Implementation Detail**: That class depends on a `Base` class that is managed +*by orogen within the `.orogen/tasks/` folder. This is how orogen manages to update +the task interface after the first generation. There are very few operations that +require a manual change in the `tasks/` folder. They are detailed in the corresponding +documentation. + ## Component/Library Separation We **strongly recommend** that you develop most of your system's functionality @@ -41,99 +185,29 @@ Developing libraries is covered in the [libraries](../libraries) section. This section deals with using library-integrated functionality and using them to build data-processing components. -## Introduction to Components +## Implementation -Within Rock, components are _implemented_ in C++. They are also _specified_ in a -Ruby domain-specific language that is processed by a code generation tool, -**oroGen**. This tool ensures that the component's interface matches its -specification. It also removes most of the crude boilerplate-writing code that -is the declaration in C++ of the component interfaces. +Once the component C++ class has been generated, you need to actually implement it. +Familiarize yourself with the Rock component's -From a package point of view, components are defined in an orogen package. The -orogen packages are all placed in the `/orogen/` subdirectory of one of the -[package categories](../workspace/conventions.html) +- [state machine](./state_machine.html) +- [how to interact with the interface elements from C++](./writing_the_hooks.html) -**Important** an oroGen package and a library can share the same basename (e.g. -`drivers/hokuyo` and `drivers/orogen/hokuyo`). This is even a recommended -behavior when an orogen package is mainly tied to a certain library. -{: .note} +And, additionally, re-read the part about [initializing properties](./interface.html#properties) -From this page on, the rest of this section will deal with the integration of -the functionality from C++ libraries into Rock components by means of orogen. -But let's first talk about how to create an orogen package. - -## Type System - -One of the first thing that a system designer has to think about is defining -the data structures that will be used to exchange data between the system's -parts (in our case, between components, and between the components and Syskit). - -These types are used for a few different things - -* in the communication between components, and between components and Syskit - (ports) -* in the configuration of the component (properties) -* to assess the component's state, i.e. diagnotics and monitoring (ports) -* to provide information about the component's internal state, i.e. debugging (ports) - -In Rock, the types are defined in C++, ideally within the libraries but -sometimes within the component packages. They are then exported into Rock's -type system to allow for their **transport** (communication between -processes), but also for their manipulation in Syskit. - -This section will detail [how types are defined](defining_types.html), how -they can [be used within oroGen packages](importing_types.html), and how -they are [mapped into the Ruby layers](types_in_ruby.html) to ease their use -on the tooling side. - -## Development Workflow - -Developing a component involves doing mainly three things: - -- [defining and importing data types](importing_types.html) for usage on - its interface. The recommended course of action for functionality developed - targetting Rock is to make sure that the library package defines C++ types - [compatible with Rock's type system](../components/defining_types.html), and - import these types directly. Alternatively, you may define types within the - oroGen package and do the conversion between the library and the Rock - type system, either manually or automatically using [opaque types](../components/importing_types.html#opaques) -- defining the component(s) interface(s) in the orogen file -- implementing the processing parts of the component in C++ - -**Let's remember** we strongly recommend that you develop the bulk of your -component's functionality in **libraries**, instead of doing in the components -themselves. -{: .important} - -Each time data types or the orogen specification are modified, one must run -orogen to re-generate code. After code generation, the package behaves like -a CMake package. - -The best way to build an oroGen package is to use -[`amake`](../basics/day_to_day.html). It takes care of code generation and -building the generated CMake package. - -## Runtime Workflow - -"Developing" a component in C++ within Rock is to write a C++ class that -interacts with its inputs/outputs. This class does not specify when the -processing is going to be called, and under which OS resource (threads, -processes). It is said that the _component implementation_ is separated -from the _system deployment_. The first one is really writing the C++ code that -interacts with the component's interface. The second one is part of system -integration. - -What it means in practice is that a component implement is nothing more than a standalone -C++ class. This C++ class can be instantiated multiple times in a single -system, using different periods or triggering mechanisms, different threading -policies, … - -When you define components in oroGen, you create a _task library_, which is a -shared library in which the task context classes are defined. Then, you need to -put these libraries in _deployments_ (which is also done by oroGen). Finally, -you can start these deployments, connect the tasks together, and monitor them -using Syskit. +We recommend that you unit-test your components. Rock has a built-in support using +Syskit as a backend. See [this page](./testing.html). -![Runtime Workflow Diagram](media/deployment_process.svg){: .fullwidth} +This documentation also provides pages on specific topics important to robotics: + +- [Timestamping of data](./timestamping.html) +- [Handling geometric transformations](./geometric_transformations.html) + +## Deployment + +In Rock, the _deployment_ is the process to how all these components will be +split in threads, processes and machines. The oroGen layer deals with the first +two, Syskit allows you to handle the third. -**Next** Let's get an overview of [type definitions](defining_types.html) +In general, with your first components, you will want to keep with [the default +deployment](./deployment.html#default). diff --git a/source/components/interface.html.md b/source/components/interface.html.md index 5bc9efb..22b3f13 100644 --- a/source/components/interface.html.md +++ b/source/components/interface.html.md @@ -48,6 +48,7 @@ Documentation for an interface element should be written as a comment directly on top of the declaration. ### Ports + Ports are defined with ~~~ ruby diff --git a/source/components/create_and_update.html.md b/source/components/orogen_packages.html.md similarity index 91% rename from source/components/create_and_update.html.md rename to source/components/orogen_packages.html.md index e49187f..767825e 100644 --- a/source/components/create_and_update.html.md +++ b/source/components/orogen_packages.html.md @@ -1,10 +1,10 @@ --- layout: documentation -title: Creating & Updating an oroGen Package +title: oroGen Packages sort_info: 3 --- -# Components +# Managing an oroGen package {:.no_toc} - TOC @@ -91,13 +91,7 @@ another oroGen package. ## Dependencies for type definitions -It is mandatory that this type of dependency defines a pkg-config file. All Rock -packages do, but 3rd party libraries may not. If they do not, you will have to -follow [this step-by-step](../libraries/cpp_libraries.html#unconventional_dependencies) to -work around these. - -Once the new pkg-config file is installed, you can refer to it with -`using_library` as described [here](./importing_types.html) +This is covered in the ["Importing types" page](importing_types.html) ## Dependencies to libraries that are used in the public interface diff --git a/source/components/state_machine.html.md b/source/components/state_machine.html.md index 589ac7b..fe31236 100644 --- a/source/components/state_machine.html.md +++ b/source/components/state_machine.html.md @@ -16,6 +16,11 @@ lifecycle state machine defines how a component can be controlled at runtime. All Rock components share the same state machine, which is what allows [the generic Syskit integration](../runtime_overview/event_loop.html). +Every transition in this state machine is given a **hook**, that is a C++ method +that will be called when the component should be performing the transition. In the +diagrams below, the transition is in _italic_ and the corresponding hook name in plain +(for instance the hook for configure is _configureHook_). + ## The nominal RTT state machine What follows is the **nominal** state machine. On each state @@ -25,11 +30,10 @@ something, i.e. the ones that you -- the component developer -- must implement if something is needed for a particular transition. The `configureHook()` and `startHook()` methods may return false, in which case -the transition is refused. +the transition is refused. Throwing an exception will have the same effect. ![The default nominal RTT state machine](media/state_machine.svg){: .fullwidth} - ### Configure and Start As its name implies, the transition between PreOperational and Stopped is meant @@ -38,9 +42,22 @@ trying to open and configure a device (which can take very long). To give you another example, in hard realtime contexts, it is expected that `startHook()` is hard realtime while `configureHook()` does not need to be. -Additionally, because of [assumptions within Syskit](../basics/recap.html), the -`configureHook()` is the only place where dynamic ports can be created (and -`cleanupHook()` the place where they must be destroyed). +There are two additional hard constraints: +1. because of [assumptions within Syskit](../basics/recap.html), the + `configureHook()` is the only place where dynamic ports can be created (and + `cleanupHook()` the place where they must be destroyed). +2. a component that becomes unused will only be stopped by Syskit, not cleaned up + (that is, "de-configured"). For this reason, any operation that needs to be done + in a stop/start cycle must be done within the `stopHook` and `startHook` + +**In summary:** + +- place in `configureHook` as much setup as possible that leaves the component in + an inactive state (essentially, not using CPU resources). +- do not put anything that would require to be redone after a stop +- place the rest of the initialization code in `startHook` +- undo everything that `startHook` does in `stopHook` +- undo everything that `configureHook` does in `cleanupHook` **Note** the `needs_configuration` statement within the file generated by `rock-create-orogen` allowed to control whether the component's requires a @@ -51,7 +68,7 @@ components should have it. ## Error representation
-![The RTT error handling](media/error_state_machine.svg) +![The RTT error handling](media/error_state_machine.svg) {: .pull-left} Errors are represented in the way depicted on the left. The exception state is @@ -70,11 +87,12 @@ of the method it is in. Make sure you return after the exception: ~~~ void Task::updateHook() { - if (something_went_wrong) - return exception(); + if (something_went_wrong) { + return exception(); + } - // Without the 'return', the execution would continue as if everything was - // alright + // Without the 'return', the execution would continue as if everything was + // alright } ~~~