- Roles of library, component and Syskit
- Design Guidelines
- Implementation using
- Syskit Integration
From the perspective of this howto, a device driver really is a piece of software that interacts with another piece of software through OS-provided I/O, that could be low-level protocols (serial) or higher-level ones (network). This howto will explain what Rock provides you to integrate this type of I/O at the library and component level. See the network design page on devices and busses for the Syskit integration of these devices
We also will provide design guidelines on how to interact with the special case of interacting with actual devices, that is the sensors and actuators that form your system.
Roles of library, component and Syskit
As with all Rock software, the library must be the place where most of the work is being done. See the introduction to the library section for the rationale behind this.
In the more particular context of a device driver, the roles of library and component are:
- library: provide an API to the device's functionality. Ideally, when it makes sense, the API should be using Rock conventions and data types. But the goal really is to expose the device functionality, without presuming too much on how this functionality might be used in your particular robotic system
- component: provide the interface(s) that the robotic system needs. What you have to realize is that if the library is well-designed, it will be easy to build system-specific tasks if needed (most of the time they're not, but bear with me).
Reading and writing threads
It is an (unfortunately) common reflex to spawn reading threads, to read the data from the device as it arrives. In the context of Rock, this threading is much more safely taken care of by the component implementation. Libraries should be "passive", that is should do something only when called, and do it in the thread of the caller.
Of course, if you need to, it is also fine to write a higher-level layer that would do the threading on top of the driver API. Just don't embed it the driver API.
Do not use saved configuration, that is configuration stored on the device. And do not assume that the device has a configuration that you already know, unless you explicitely reset it to default configuration each time the device connects to it (a valid practice in some cases).
Use retries only as a very last resort (i.e. to workaround very broken devices). The system should be the one dealing with errors. Fail early.
Let's start with the first level of implementation, the library. Rock has
a very neat library meant to deal with I/O,
you really know what you are doing, we very strongly suggest you start by
basing your device driver on
Check out its README for documentation.
As is usual within Rock, the
drivers/iodrivers_base library has an equivalent
Two steps are needed to integrate a device driver in your Syskit environment:
- define the supported device's model (if needed)
- declare that your component is a driver for the device
Then, to use a device, one declares it in a profile's
robot context, as we
have seen in the Syskit basics tutorial. Between this
declaration and the loading of a compatible device driver component (with
using_task_library), Syskit will be able to inject the device in your component
We have seen device models in the Syskit basics tutorial. The role of a device model is to represent an actual device in a network, without necessarily picking how this device will be interfaced with the Syskit system just yet.
Because device models represent actual devices, the guideline for device naming is to categorize them by manufacturer and model. This makes creating the robot block declaration in the system's base profile much easier, as it leaves little to guessing.
In practice, the device model hierarchy should follow the
App::Devices::Type::Manufacturer::Model pattern, for instance
CommonModels::Devices::Sonar::Tritech::Micron. As an example, let's declare
the M8-class chip from Ublox.
To create the new device model, one would run
syskit gen dev GPS::Ublox::M8
Within the device model, one declares the services that all drivers for this device must provide (they may provide more). Let's edit the newly created `models/devices/gps/ublox/m8.rb' file and modify the device declaration
require 'common_models/services/pose' module MyApp module Devices module GPS module Ublox device_type 'M8' do provides CommonModels::Services::GlobalPosition provides CommonModels::Services::GPS end end end end end
Device Driver Component
A device driver component is an oroGen component that has been declared as being able to drive a device. This is done in the oroGen extension file.
Let's expand on our hypothetical Ublox M8. If we assume that we have written a
gps_ublox::M8Task component to drive it, we can generate the orogen extension
syskit gen orogen gps_ublox
And edit the created
models/orogen/gps_ublox.rb file to add:
require 'models/devices/gps/ublox/m8' Syskit.extend_task OroGen.gps_ublox.M8Task do driver_for MyApp::Devices::GPS::Ublox::M8, as: 'ublox_m8' end
This way, when you load the component model using
Syskit will be aware that the
M8Task component can be used to drive a M8 device.
On the usage side, one needs to declare the devices that are available on the
device. Edit your robot's
Base profile, and declare the device. This
gps_dev entry in the profile, that can be used directly in
the Syskit IDE and/or used in dependency injection in the other profiles.
profile 'Base' do robot do device Devices::GPS::Ublox::M8, as: 'gps' end end
At runtime, the device configuration is done through the standard orogen
configuration file(s). The device URI
(as supported by
is given in the
io_port property. Drivers should be designed so that only setting
this property should be enough to have a functional component.
Syskit will automatically pick a configuration with the device's name if one is available, e.g. with a configuration file containing two sections
--- name:default --- name:gps
Syskit will use the
['default', 'gps'] configuration by default (since the
device declared in the
robot block is called
gps) for the driver. If the
gps configuration does not exist, it will simply use
In addition to the URI mechanism, the oroGen integration also allows you to
communicate with the driver through the
component ports. To use this, connect the component that will handle the byte
streams to these two ports and leave
iodrivers_base-based components "replicate" the data they
exchange on their
io_write_listener ports. This can
generate a lot of log data. We recommend that you configure Syskit to not
log this by default using Syskit's log groups:
Robot.config block of your robot configuration file,
Syskit.conf.logs.create_group 'RawIO', enabled: false do |g| g.add /iodrivers_base.RawIO/ end
You may then re-enable logging using the Syskit IDE for better debugging
iodrivers_base-based components output a
status structure on the
io_status port. When things misbehave, this
structure allows you to determine whether
- the problem is that a device stopped sending data (
- the communication channel is bad or the packet extraction logic has a bug
bad_rx is high)
- the component stopped sending data even though it should not have (