Skip to content
Shahbaz Youssefi edited this page Jun 3, 2015 · 3 revisions

Driver's Interaction with Skinware

In the previous page in this tutorial, the tasks of the driver were explained. In this page, it is shown what API the driver could use to perform those tasks. This is not an exhaustive list of available API.

Starting Up Skinware

Before using Skinware, it needs to be set up using skin_init() and in the end, cleaned up using skin_free(). Please see details in this small page.

Declaring the Driver to Skinware

Once the driver has discovered and initialized the robot skin, it is time to declare itself to Skinware. This is done with the skin_driver_add() function, which takes a fair bit of information. First, let's take a look at the prototype of this function and a sample usage:

struct skin_driver *skin_driver_add(struct skin *skin,
                                    const struct skin_driver_attr *attr,
                                    const struct skin_writer_attr *writer_attr,
                                    const urt_task_attr *task_attr,
                                    const struct skin_driver_callbacks *callbacks);

skin_driver_add(skin,
                &(struct skin_driver_attr){
                     .sensor_count = total_sensor_count,
                     .module_count = total_module_count,
                     .patch_count = total_patch_count,
                 },
                &(struct skin_writer_attr){
                     .name = "TST",
                 },
                &(urt_task_attr){
                     .period = 1000000000 / acquisition_rate,
                 },
                &(struct skin_driver_callbacks){
                     .details = driver_details,
                     .acquire = driver_acquire,
                     .user_data = data,
                 });

Advanced Tip

The prototype of skin_driver_add is in fact the following:

struct skin_driver *(skin_driver_add)(struct skin *skin,
                                      const struct skin_driver_attr *attr,
                                      const struct skin_writer_attr *writer_attr,
                                      const urt_task_attr *task_attr,
                                      const struct skin_driver_callbacks *callbacks,
                                      int *error, ...);

With a bit of macro magic, the last parameter (error) is made optional and defaults to NULL. If provided, it will return the reason why the function has failed, it if fails. The same mechanism is used with skin_init().

In the above example, the following information is provided by the driver to Skinware:

  • The total number of sensors, modules and patches in the skin,
  • The name of the driver,
  • The period of data acquisition,
  • Callbacks that provide details of the structure of the skin and acquire the data. A user defined pointer is taken as well to be given to the callbacks to avoid having to use global variables.

The reason the function call looks rather complicated is that the driver could in fact further control its creation, for example by specifying the number of buffers used for communication or the priority, offset or stack size of the acquisition task etc. In the above example, all those values are set to zero, and Skinware uses the default value. It would also help you understand the drivers better, by knowing that they are built on top of services. See the tutorial on services for more information.

If the function above returns NULL, the driver has not been created due to an error.

Once the driver is created, its task is started in suspended mode. To actually make the driver run, it needs to be resumed. This can be done through the skin_driver_resume() function, if the return value from skin_driver_add() is kept, or more easily by resuming all tasks in the skin:

skin_resume(skin);

The details() Callback

The details() callback is responsible for providing details on the structuring of the skin. These details include the number of modules in each patch, the number of sensors in each module and the type and unique identifier of each sensor in each module.

The prototype for this function should be as follows:

int driver_details(struct skin_driver *driver, bool revived, struct skin_driver_details *details, void *user_data);

This callback is given the following information:

  • The handle to the driver, most likely unused in this callback,
  • Whether the driver is new, or being revived. See the advanced tip below,
  • Details to be filled,
  • User defined pointer that was given to skin_driver_add(), to avoid having to use global variables.

The struct skin_driver_details variable that needs to be filled, has the following structure:

struct skin_patch_decl
{
    skin_module_size module_count;
};

struct skin_module_decl
{
    skin_sensor_size sensor_count;
};

struct skin_sensor_decl
{
    skin_sensor_unique_id uid;
    skin_sensor_type_id type;
};

struct skin_driver_details
{
    struct skin_driver_attr overall;
    struct skin_patch_decl *patches;
    struct skin_module_decl *modules;
    struct skin_sensor_decl *sensors;
};

The overall structure of the skin is already known, which was given to skin_driver_add(). patches, modules and sensors point to memory already allocated by Skinware. The driver thus needs to fill in the following data:

  • patches[i].module_count for 0 <= i < overall.patches_count,
  • modules[i].sensor_count for 0 <= i < overall.modules_count,
  • sensors[i].type and sensors[i].uid for 0 <= i < overall.sensors_count.

The modules of patches are laid out patch after patch, and their sensors are laid out one after the other. The following image shows these three arrays and their relationships.

Why Skinware

(Note: some browsers can't correctly render SVG files)

Skinware verifies that the sum of the module or sensor counts provided in the details match the overall values.

Advanced Tip

If the driver has been detached from Skinware and is being reattached before its users have detached from it, it needs to be representing the exact same piece of robot skin. In this case, the driver is said to be revived. The details() callback in this case, i.e., when the revived parameter is set, needs to verify that what Skinware remembers of the robot skin and what the driver has rediscovered match.

To perform this task, the information requested above is already present in the struct skin_driver_details parameter. The driver would thus need to verify these information (instead of writing them). In case of a mismatch, the driver should return a non-zero value to stop the process of reattaching the driver.

The acquire() Callback

Once the driver is successfully attached to Skinware and is resumed, the acquire() callback will be periodically called by the data acquisition task within Skinware. At this point, the buffer inside which the callback should write the sensor data is already locked.

The prototype for this function should be as follows:

int driver_acquire(struct skin_driver *driver, skin_sensor_response *responses, skin_sensor_size sensor_count, void *user_data);

This callback is given the following information:

  • The handle to the driver, most likely unused in this callback (but see the advanced tip below),
  • The buffer to fill with sensor responses. The order of the sensors is the same as filled in details->sensors array discussed in the previous section,
  • Total number of sensors. The driver should already know this,
  • User defined pointer that was given to skin_driver_add(), to avoid having to use global variables.

If this function returns a non-zero value, the driver is marked as bad. An external program could monitor for bad drivers (using the skin_info tool) and try to restart them. Consequently, once the driver is removed, it is marked as inactive. This warns the users so they know no data is arriving from this driver. The users may then decide to detach from the inactive driver altogether, which is a good idea since it allows the driver to, upon restart, represent a possibly different robot skin, for example a subset of the previous robot skin because parts of it are damaged and currently unavailable.

Advanced Tip

The driver is assumed to provide new data for all sensors of the skin when the acquire() callback is called. If for any reason the driver is not able to provide new data for all sensors, or deems unnecessary to do so, it would need to specifically copy data from the previous data buffer into the current buffer. If the driver ignores this step and simply writes only parts of the current buffer, the other parts of the buffer may contain boundlessly old data, which can be very confusing for the end-user.

To copy the data from the previous buffer to the current one, before writing the new data from hardware, simply do:

skin_driver_copy_last_buffer(driver);

If your driver always acquires data from the whole robot skin with each call to acquire(), there is no need for the call to skin_driver_copy_last_buffer(), and in fact that would be inefficient.


Next: Build a complete simulation driver.

Clone this wiki locally