Dependencies:   mbed

Dependents:   HvZ

XBee.hpp

Committer:
etherealflaim
Date:
2010-12-01
Revision:
1:eefaa22e4b2c
Parent:
0:86ff0a55c978
Child:
2:2a826741387f

File content as of revision 1:eefaa22e4b2c:

#ifndef XBEE_HPP
#define XBEE_HPP

#include <queue>
#include <cctype>
#include <string>
#include <vector>
#include <cstring>

#include "mbed.h"
#include "handler.hpp"

#define PRINT(c) ((isprint(c))?(c):('.'))

/** This class encapsulates a simplistic XBee packet.
 * 1. The packet is 0x01 delimited.
 * 2. The packet has a source and destination, which are (nominally) 8-byte identifiers
 */
class XBeePacket
{
private:
    string m_data;
    int    m_data_len;
    string m_source;
    string m_dest;
    bool   m_packed;
    
public:
    /// Construct an empty (invalid) XBeePacket
    inline XBeePacket()
    : m_data(""), m_data_len(0), m_source("_INVALID"), m_dest("********"), m_packed(false)
    {
    }
    
    /// Create an XBeePacket with the given data
    inline XBeePacket(const string data)
    : m_data(data), m_data_len(data.length()), m_source("_INVALID"), m_dest("********"), m_packed(false)
    {
    }
    
    /// Create an XBeePacket with the given source and data
    inline XBeePacket(const string src, const string data)
    : m_data(data), m_data_len(data.length()), m_source(src), m_dest("********"), m_packed(false)
    {
    }
    
    /// Create an XBeePacket with the given source, destination, and data
    inline XBeePacket(const string src, const string dst, const string data)
    : m_data(data), m_data_len(data.length()), m_source(src), m_dest(dst), m_packed(false)
    {
    }
    
    /// Append the given data to the packet
    inline void append(const string data)
    {
        m_data += data;
        m_data_len += data.length();
    }
    
    /// Get the packet source (if set or unpacked)
    inline string source() { return m_source; }
    
    /// Set the packet source (before packing)
    inline void source(string src) { m_source = src; }
    
    /// Get the packet destination (if set or unpacked)
    inline string dest() { return m_dest; }
    
    /// Set the packet destination (before packing)
    inline void dest(string dst) { m_dest = dst; }
    
    /// Get the packet data (if set or unpacked)
    inline string data() { return m_data; }
    
    /// Set the packet data (before packing)
    inline void data(string dat) { m_data = dat; }
    
    /// Get a vector of the 0x01-delimited pieces of the data (after unpacking)
    inline vector<string> split_data()
    {
        vector<string> pieces;
        int begin = 0;
        int end = 0;
        while (end < m_data_len && (end = m_data.find(0x01, begin)) != string::npos)
        {
            pieces.push_back( m_data.substr(begin, end-begin) );
            begin = end+1;
        }
        pieces.push_back( m_data.substr(begin) );
        return pieces;
    }
    
    /// Clear the packet (zero the length, and reset the source and destination)
    inline void clear()
    {
        m_data = "";
        m_data_len = 0;
        m_source = "_CLEARED";
        m_dest   = "********";
    }
    
    /** Pack the packet with header, etc.
     * This function can be called separately to include the headers in, e.g., a dump()
     */
    inline void pack()
    {
        string header = "iHvZ\001" + m_source + "\001" + m_dest + "\001";
        m_data = header + m_data;
        m_data_len += header.length();
        m_packed = true;
    }
    
    /** Unpack the packet header
     * This function should be called with received packets to extract the header
     */
    inline bool unpack()
    {
        if (m_data.substr(0, 5) != "iHvZ\001") return false;
        int source_idx = m_data.find(0x01, 0)          + 1;
        int dest_idx   = m_data.find(0x01, source_idx) + 1;
        int data_idx   = m_data.find(0x01, dest_idx)   + 1;
        if (source_idx == string::npos ||
            dest_idx   == string::npos ||
            data_idx   == string::npos) return false;
        
        #if _XBEE_DEBUG > 1
        Serial usb(USBTX,USBRX);
        usb.printf("-- UNPACK:\r\n");
        usb.printf("-- FULL    = %s\r\n", m_data.c_str());
        usb.printf("-- INDICES = src %d / dst %d / dat %d\r\n", source_idx, dest_idx, data_idx);
        usb.printf("-- SPLIT   = %s / %s / %s\r\n",
          m_data.substr(source_idx, dest_idx - source_idx - 1).c_str(),
          m_data.substr(dest_idx, data_idx - dest_idx - 1).c_str(),
          m_data.substr(data_idx));
        #endif
        
        m_source = m_data.substr(source_idx, dest_idx - source_idx - 1);
        m_dest   = m_data.substr(dest_idx, data_idx - dest_idx - 1);
        m_data   = m_data.substr(data_idx);
        m_data_len = m_data.length();
        return true;
    }
    
    /// Send the packet on the given Serial port (automatically packs if unpacked)
    inline void send(Serial &out)
    {
        if (!m_packed) pack();
        
        out.puts(m_data.c_str());
        out.putc('\n');
    }
    
    /// Dump a text version (call before packing) of the packet to the serial port
    inline void dump(Serial &out)
    {
        out.printf("[%s] -> [%s] : [",  m_source.c_str(), m_dest.c_str());
        for (int i = 0; i < m_data_len; ++i)
        {
            char c = m_dest[i];
            if (isprint(c))
                out.putc(c);
            else
                out.printf("\\x%02X", c);
        }
        out.printf("] %d bytes\r\n", m_data_len);
    }
    
    /// If it is a broadcast packet (destionation ********) return true
    inline bool is_broadcast() { return m_dest == "********"; }
};

/** Manage the pins connected to an XBee chip in transparent (broadcast) mode.
 * This is an interface for communicating XBeePackets across the XBee chip in transparent mode.
 * Receipt of a new packet can be either polled or notified via the attach() functions.
 */
class XBee
{
private:
    /* Unit ID */
    string m_uid;

    /* Pins */
    PinName m_xout, m_xin, m_xrstn, m_xassoc, m_xon;
    
    /* Pin handlers */
    DigitalOut m_resetn;
    DigitalIn  m_ison;
    Serial     m_comm;
    
    /* USB logging */
    Serial     m_usb;
    
    /* Tickers and Timers */
    Timeout     m_timeout;
    
    /* Packet incoming queue */
    queue<XBeePacket> m_queue;
    handler<void> *m_read_handler;

public:
    /** Create an XBee chip connect
     * uid       - The unit ID of the XBee chip (used in all outgoing XBeePackets)
     * xbee_din  - The MBED pin connected to the XBEE DIN pin
     * xbee_dout - The MBED pin connected to the XBEE DOUT pin
     * xbee_rst  - The MBED pin connected to the XBEE nRST pin
     * xbee_on   - The MBED pin connected to the XBEE ON/nSLEEP pin
     */
    inline XBee(string uid, PinName xbee_din, PinName xbee_dout, PinName xbee_rst, PinName xbee_on)
    : m_uid(uid), m_xout(xbee_din), m_xin(xbee_dout), m_xrstn(xbee_rst), m_xon(xbee_on),
      m_resetn(xbee_rst), m_ison(xbee_on), m_comm(xbee_din, xbee_dout),
      m_usb(USBTX, USBRX), m_read_handler(NULL)
    {
        #if _XBEE_DEBUG > 0
        m_usb.printf("Resetting XBee...\r\n");
        #endif
        
        // Reset the XBEE module
        m_resetn = 0;
        wait(.1);
        m_resetn = 1;
        wait(1);
        
        // Set up serial communications (9600 baud by default)
        m_comm.baud(9600);
        
        // Set up a handler for serial communications
        m_comm.attach(this, &XBee::rxirq);
        
        // Check sleep status
        #if _XBEE_DEBUG > 0
        m_usb.printf("XBee is %s\r\n", (m_ison)?"ON":"SLEEP");
        #endif
        
        setup();
    }
    
    inline void broadcast(string data)
    {
        XBeePacket pkt(data);
        pkt.source(m_uid);
        pkt.send(m_comm);
    }
    
    inline void send(string to, string data)
    {
        XBeePacket pkt(data);
        pkt.source(m_uid);
        pkt.dest(to);
        pkt.send(m_comm);
    }
    
private:

    inline void setup()
    {
        #if _XBEE_DEBUG > 0
        m_usb.printf("Setting up XBee...\r\n");
        #endif
        
        // Turns out, we can only really use this in broadcast mode.
        //commandMode();
        //command("VR");
        //command("AP", 1);
        //command("CN");
        //wait(1);
        //m_usb.printf("Setup Complete.\r\n");
        
        m_timeout.attach(this, &XBee::tickirq, .1);
    }
    
    /** Enter command mode.  Send command("CN") to exit command mode. */
    inline void commandMode()
    {
        wait(1.1);
        m_comm.printf("+++");
        wait(1.1);
    }
    
    inline void command(const char *cmd)
    {
        m_comm.printf("AT%s\r", cmd);
        wait(.001);
    }
    
    inline void command(const char *cmd, int arg)
    {
        m_comm.printf("AT%s%X\r", cmd, arg);
        wait(.001);
    }

    inline void rxirq()
    {
        static XBeePacket nextPacket;
        static char buffer[256];
        char *p = buffer;
        bool finished = false;
        
        // Receive all characters in buffer
        do
        {
            // Receive character
            *p = m_comm.getc();
            
            // Check if it's end-of-packet
            if (*p == '\n') {
                finished = true;
                break;
            }
            
            // Move on to the next character
            ++p;
        } while (m_comm.readable() && p-buffer < 255);
        
        // Null terminate
        *p = '\0';
        
        // Append the characters to the packet
        nextPacket.append(buffer);
        
        if (finished)
        {
            // Unpack the headers
            if (!nextPacket.unpack()) {
                m_usb.printf("Received garbled packet; dropping.");
                return;
            }
            
            if (nextPacket.is_broadcast() || nextPacket.dest() == m_uid)
            {
                #if _XBEE_DEBUG > 0
                // Print the packet
                m_usb.printf("Received: ");
                nextPacket.dump(m_usb);
                #endif
                
                // Add it to the queue
                m_queue.push(nextPacket);
            }
            else
            {
                #if _XBEE_DEBUG > 0
                m_usb.printf("Dropping: ");
                nextPacket.dump(m_usb);
                #endif
            }
            
            // Clear the packet
            nextPacket = XBeePacket();
        }
    }
    
    inline void tickirq()
    {
        //m_usb.printf("Scanning...\r\n");
        //XBeePacket pkt = XBeePacket::Command("ND");
        //XBeePacket pkt = XBeePacket::Broadcast("Test");
        //pkt.dump(m_usb);
        //pkt.send(m_comm);
        //m_comm.printf("TEST\r");
        //XBeePacket pkt("TestPacket");
        //m_usb.printf("Sending: ");
        //pkt.dump(m_usb);
        //pkt.send(m_comm);
        
        while (!m_queue.empty() && m_read_handler)
            (*m_read_handler)();
        
        // Set the timeout again (we aren't going to use a ticker just in case the read handlers take awhile)
        m_timeout.attach(this, &XBee::tickirq, 0.1);
    }
    
    
public:

    /// Return whether or not another XBeePacket is ready
    inline bool readable()
    {
        return !m_queue.empty();
    }
    
    /// Get the next XBeePacket in line (make sure it's readable()!)
    inline XBeePacket read()
    {
        XBeePacket pkt = m_queue.front();
        m_queue.pop();
        return pkt;
    }
    
    /// Set the Unit ID
    inline string uid() { return m_uid; }
    
    /// Get the Unit ID
    inline void uid(string uid) { m_uid = uid; }
  
    /// Attach a member function to be called on all TCP packets
    template <class T>
    inline void attach(T *inst, void (T::*func)())
    {
        delete m_read_handler;
        m_read_handler = new member_handler<T,void>(inst, func);
    }
    
    /// Attach a non-member function to be called on all TCP packets
    inline void attach(void (*func)())
    {
        delete m_read_handler;
        m_read_handler = new function_handler<void>(func);
    }
    
    /// Detach the handler
    inline void detach()
    {
        delete m_read_handler;
        m_read_handler = NULL;
    }
};

#endif /* XBEE_HPP */