Writing a new device driver
How to use the DevBot infrastructure to create drivers for your custom hardwareIt's pretty important that you've read the Architectural overview document, but you should also have read, and be familiar with developing controllers with libbot. This tutorial digs under DevBot's hood, so things become a little hairy in places.
Contents
- A minimal driver
- Compiling a driver
- Private data and configuration
- Implementing an interface
- Device specific functionality
- Bringing it all together
A minimal driver
Let's look at a minimal driver:
#include <devbot/core/driver.h> static gpointer get_interface(BotDevice *device, BotInterface *interface) { return NULL; } BOT_DRIVER_BOILERPLATE("minimal", /* Name */ "A minimal driver", /* Description */ "1.0", /* Version string */ NULL, /* Driver initializer */ NULL, /* Driver destructor */ NULL, /* Device initializer */ NULL, /* Device destructor */ &get_interface)
get_interface() is used to implement bot_device_get_interface() for devices using this driver.
Note that get_interface is defined statically. It's important to do this with all functions and variables used in driver since they are loaded into the global namespace!
BOT_DRIVER_BOILERPLATE is a convenience macro used to facilitate setting up a driver. Note the absence of a semicolon!
Compiling a driver
Gotchas:
- BOT_INTERNAL_API must be defined
- Drivers are typically called drivername.so (not libdrivername.so)
- Compiled drivers located must be on LD_LIBRARY_PATH on order to be found by the
Private data and configuration
Devices typically require some private (or per-device) data. Often this will correspond to the configuration information, so for example, a device configuration might specify a (Linux) device node such as "/dev/blah0". A handle for this device could then be stored in the private device data.It is strongly recommended that devices utilise opaque data structures for private data.
Private data is created by the "device initializer" referred to above and freed by the "device destructor" function. Here's a very simple example:
struct _MyPrivateData { int my_own_private_integer; } typedef struct _MyPrivateData MyPrivateData; gboolean initalize_device_data(gpointer *data, BotDeviceConfiguration *configuration) { *data = g_new0(MyPrivateData, 1); data -> my_own_private_integer = 1337; return TRUE; } gboolean free_device_data(gpointer *data) { g_free(data); }
g_new0 and g_free are just Glib malloc/free wrappers.
Implementing an interface
In order to implement an interface one must be familiar with the capabilities it exposes. These are can be found in the header file corresponding to that interface.Let's implement the BotRawInterface. BotRawInterface "supports" just one function
double bot_raw_get_value(BotDevice *device);
This can be implemented in a driver as follows
static double get_value(BotDevice *device) { return (double) 1337; } static BotRaw rawInterface = { .get_value = &get_value; } static gpointer get_interface(BotDevice *device, BotInterface *interface) { if (BotRawInterface==interface) return &rawInterface; return NULL; }
- Warning:
- This boilerplate code will probably be replaced by a very small macro at some point, but it's interesting to peek under the hood isn't it?
Device specific functionality
Bringing it all together
/****************************************************************************** * * This is a demo of how to implement a driver-device plugin. * * The idea is to use every single feature of the API. * * Yes, the debugging output is excessive. * *******************************************************************************/ #include <stdlib.h> #include <devbot/core/driver.h> #include <devbot/distance.h> #define _DEBUG_TRACE(...) g_debug(__VA_ARGS__) /******************************************************************************* * Driver implementation *******************************************************************************/ static gboolean initialize_driver(void) { _DEBUG_TRACE("initialize_driver"); /* Perform initialization here e.g. registering with other drivers, initializing other libraries */ return TRUE; } static gboolean free_driver(void) { _DEBUG_TRACE("free_driver"); /* Perform any cleanups necessitated by initialization process */ return TRUE; } struct _privateDeviceData { int call_count; }; typedef struct _privateDeviceData privateDeviceData; static gboolean initialize_device_data (gpointer *data, BotDeviceConfiguration *configuration) { _DEBUG_TRACE("create_device_data"); /* 1. Allocate space for our private data */ privateDeviceData *my_data = g_new0(privateDeviceData, 1); if (my_data == NULL) goto creation_failed; /* 2. Use configuration to populate it */ gchar *value; gchar *tmp; _DEBUG_TRACE(" -> Configuring device"); if (configuration == NULL) { g_warning("debug devices require configuration"); goto creation_failed; } /* 2a. A required configuration value */ _DEBUG_TRACE(" -> Looking for Required_value"); value = bot_configuration_get_value(configuration, "Required_value"); if (value == NULL) { g_warning("debug devices must specify a value for the 'Required_value' option"); goto creation_failed; } _DEBUG_TRACE(" -> Looking for Initial_call_count"); /* 2b. An optional configuration value */ value = bot_configuration_get_value(configuration, "Initial_call_count"); if (value != NULL) { my_data->call_count = (int) strtol(value, &tmp, 0); _DEBUG_TRACE("call_count set to %d by configuration", my_data->call_count); } /* 3. Set the device data pointer to our newly constructed data */ *data = my_data; _DEBUG_TRACE(" -> Initalization complete"); return TRUE; creation_failed: _DEBUG_TRACE("Failed to create debug device (see above for reason)"); g_free(my_data); return FALSE; } static gboolean free_device_data (gpointer data) { _DEBUG_TRACE("free_device_data"); g_free(data); return 0; } static gpointer get_interface (BotDevice *device, BotInterface *type); BOT_DRIVER_BOILERPLATE("debug", "A maximal driver, demonstrating all the features of the driver API", "1.0", &initialize_driver, &free_driver, &initialize_device_data, &free_device_data, &get_interface); /******************************************************************************* * Interface definitions *******************************************************************************/ /* Forward declarations of supported interfaces */ BotDistance _distanceInterface; /* Return an interface structure of the specified type or NULL if not available */ static gpointer get_interface(BotDevice *device, BotInterface *type) { _DEBUG_TRACE("get_interface (%s)", type->name); if (BotDistanceInterface == type) return &_distanceInterface; return NULL; } /******************************************************************************* * * Rangefinder implementation * *******************************************************************************/ static double get_metre(BotDevice *device) { _DEBUG_TRACE("get_distance"); return ((privateDeviceData *) bot_device_get_data(device))->call_count++; } BotDistance _distanceInterface = { .get_metre = &get_metre };
(c) 2006 Edinburgh Robotics Ltd.
Generated on Fri Feb 2 11:24:07 2007 for libbot by doxygen 1.5.1