Internal Functional Overview

Before discussing the internals of libxbee, it might help to understand the problem. XBee modules provide a bridge between a TTL serial interface and the wireless network. libxbee communicates with the serial interface, using API frames that are outlined in the various manuals. Each XBee module has a different set of API frames avaliable which allow different types of communication (e.g: 'Local AT', 'Remote AT', '64-bit Addressed Data', etc...).

In order to maintain flexibility, I decided that as much functionality as possible would be implemented in one point, and that would form the core of libxbee. This results in having an extremely simple and expandable library - libxbee.

Contents

Supporting Different Modules

As I'm sure you're aware, there is a range of modules to choose from. Each type of module provides a different (and mostly incompatible) selection of API frames. The easiest way to support these different modules is to create small module-specific bits. The example code below shows the basic principal behind this approach.

Example

This source has been simplified for explanatory purposes.

  1. struct xbee_mode {
  2.   char *name;
  3.  
  4.   struct xbee_modeConType **conTypes;
  5.  
  6.   /* retrieves raw buffers from the I/O device (XBee) */
  7.   xbee_err (*rx_io)(struct xbee *xbee, void *arg, struct xbee_buf **buf);
  8.   /* transmits raw buffers to the I/O device (XBee) */
  9.   xbee_err (*tx_io)(struct xbee *xbee, void *arg, struct xbee_buf *buf);
  10. };
  11.  
  12. /* ######################################################################### */
  13.  
  14. /* link a list of avaliable connection types and the Tx/Rx handlers to an
  15.    interface mode */
  16. struct xbee_mode mode_xbee1 = {
  17.   .name = "xbee1",
  18.  
  19.   .conTypes = conTypes,
  20.  
  21.   .rx_io = xbee_xbeeRxIo,
  22.   .tx_io = xbee_xbeeTxIo,
  23. };
  24.  
  25. /* ######################################################################### */
  26.  
  27. /* create a list of avaliable interface modes */
  28. struct xbee_mode *modeList[] = {
  29.   &mode_xbee1,
  30.   /* ... and the other supported modules ... */
  31.   NULL
  32. };

Using this approach, it is extremely easy to locate and then use the required functionality for a given interface. The conTypes variable used above will be discussed in the Allowing Easy Access to Multiple Connection Types section. The xbee_xbeeRxIo() and xbee_xbeeTxIo() functions provide the interface between a raw byte stream of the I/O device itself, and a stream of packets.

Allowing Easy Access to Multiple Connection Types

I have implemented the support for multiple connection types in a very similar fashion to that described in the Supporting Different Modules section. As the funcional core of libxbee is as flexible as possible, the resulting requirements for a new connection type to be implemented basically come down to knowing its name, and providing a couple of simple functions. These functions are able to convert the packet's raw payload for a given API frame type into a struct xbee_pkt for an Rx handler, or vice-versa for a Tx handler. The functions are then linked with the appropriate API frame ID, then assigned to a connection type.

Example

This source has been simplified for explanatory purposes.

  1. struct xbee_modeDataHandlerRx {
  2.   unsigned char identifier;
  3.   xbee_err (*func)(struct xbee *xbee,
  4.        /* IN */    struct xbee_buf *buf,
  5.        /* OUT */   struct xbee_frameInfo *frameInfo,
  6.        /* OUT */   struct xbee_conAddress *address,
  7.        /* OUT */   struct xbee_pkt **pkt);
  8. };
  9.  
  10. struct xbee_modeDataHandlerTx {
  11.   unsigned char identifier;
  12.   xbee_err (*func)(struct xbee *xbee,
  13.                    struct xbee_con *con,
  14.       /* IN */     unsigned char frameId,
  15.       /* IN */     struct xbee_conAddress *address,
  16.       /* IN */     struct xbee_conSettings *settings,
  17.       /* IN */     const unsigned char *buf,
  18.       /* IN */     int len,
  19.       /* OUT */    struct xbee_buf **oBuf);
  20. };
  21.  
  22. struct xbee_modeConType {
  23.   char *name;
  24.   struct xbee_modeDataHandlerRx *rxHandler;
  25.   struct xbee_modeDataHandlerTx *txHandler;
  26. };
  27.  
  28. /* ######################################################################### */
  29.  
  30. /* these function prototypes may look scary, but they form an extremely
  31.    flexible interface */
  32. xbee_err local_at_rx_handler(struct xbee *xbee, struct xbee_con *con,
  33.   unsigned char frameId, struct xbee_conAddress *address,
  34.   struct xbee_conSettings *settings, const unsigned char *buf, int len,
  35.   struct xbee_buf **oBuf) {
  36.   /* do some Rx magic... */
  37. }
  38. xbee_err local_at_tx_handler(struct xbee *xbee, struct xbee_con *con,
  39.   unsigned char frameId, struct xbee_conAddress *address,
  40.   struct xbee_conSettings *settings, const unsigned char *buf, int len,
  41.   struct xbee_buf **oBuf) {
  42.   /* do some Tx magic... */
  43. }
  44.  
  45. /* link the handlers to the API frame IDs */
  46. struct xbee_modeDataHandlerRx local_at_rx = {
  47.   .identifier = 0x88,
  48.   .func = local_at_rx_handler
  49. };
  50. struct xbee_modeDataHandlerTx local_at_tx = {
  51.   .identifier = 0x08,
  52.   .func = local_at_tx_handler
  53. };
  54.  
  55. /* link the handlers to a connection type */
  56. struct xbee_modeConType local_at = {
  57.   .name = "Local AT",
  58.   .rxHandler = &local_at_rx,
  59.   .txHandler = &local_at_tx
  60. };
  61.  
  62. /* ######################################################################### */
  63.  
  64. /* create a list of avaliable connection types */
  65. struct xbee_modeConType *conTypes[] = {
  66.   &local_at,
  67.   /* ... and the other connection types ... */
  68.   NULL
  69. };

Now, on recieving an API frame, you simply need to lookup which handler to use from the list, provide it with some data, and use the data that it returns. Similarly, when transmitting a packet, you looking the handler, give it the data and it returns an API frame.

Providing a Connection-Oriented Interface

As we have already linked a given API frame ID with a connection type (see Allowing Easy Access to Multiple Connection Types), we already have an idea of how to form a connection. In the majority of cases, an address will uniquely identify one node from another (I say majority, because some connection types don't use addressing at all - 'Local AT'). This makes it simple to match recieved packets with an existing connection, and when xbee_conTx() is called, we already know what address to use in the transmission.

Knowing this, connections are simply formed of a list of incoming packets. Packets are matched to connections by the core libxbee functionality, and the new message is appended to the list. When the developer then calls xbee_conRx(), the first packet in the list is removed from the list and handed over.

Transmissions occur in a slightly different manner. When xbee_conTx() is called, the data is assembled, and passed along with the connection's address to the Tx handler (mentioned in the Allowing Easy Access to Multiple Connection Types section). This handler is then responsible for assembling the given information into an API frame. The returned API frame is then added to a transmission queue, and waits patiently to be transmitted.

Making Use of Status Reports

Some XBee API frames provide transmission status reports. These are particularly useful if you need to ensure that data reaches its destination, and in the event that it doesn't, take some other action. If the given connection type supports the feature, then libxbee will wait for the report by default. This means that a call to xbee_conTx() will wait, and return an error if appropriate.

This feature is implemented using semaphores. Immediately after transmission, libxbee waits on the connection's semaphore. When the matching transmission status information is provided by the XBee, libxbee posts the waiting connection's semaphore.

Oh if only it were that simple. The XBee modules allow you to specify a frame ID that can have a range of 1-255. This frame ID is then used to match a status report, with an attempted transmission. The example code below gives you an idea of how they are used. The xbee_frameGetFreeID() function locates a free frame ID, and the xbee_frameWait() function waits on the semaphore with a give timeout before tidying up.

Example

This source has been simplified for explanatory purposes.

  1. xbee_err xbee_connTx(struct xbee_con *con, unsigned char *retVal, const unsigned char *buf,
  2.   int len) {
  3.   xbee_err ret;
  4.  
  5.   xbee_mutex_lock(&con->txMutex);
  6.  
  7.   if (con->conType->allowFrameId) {
  8.     /* this function assigns 'con' a frameID */
  9.     xbee_frameGetFreeID(con->xbee->fBlock, con);
  10.   }
  11.  
  12.   /* this function digs down, locates the connection type's Tx handler and calls it */
  13.   if ((ret = xbee_txHandler(con, buf, len)) != XBEE_ENONE) goto done;
  14.  
  15.   if (con->conType->allowFrameId) {
  16.     struct timespec to;
  17.     clock_gettime(CLOCK_REALTIME, &to);
  18.     to.tv_sec += 1; /* default 1 second timeout */
  19.     if (xbee_frameWait(con->xbee->fBlock, con, retVal, &to) != XBEE_ENONE ||
  20.         *retVal != 0) {
  21.       ret = XBEE_ETX;
  22.     }
  23.   }
  24.  
  25. done:
  26.   xbee_mutex_unlock(&con->txMutex);
  27.  
  28.   return ret;
  29. }

Supporting the Connection Callback Feature

The callback functionality is implemented using threads. When data is recieved for a connection that has callbacks enabled, a thread is started which will then pass the received data to the configured callback function. A connection's callback thread will be active until all of the data has been processed. At this point, the thread will idle for a short period of time, before destroying itself.