This library contains a simple device driver for the X10 CM17a module. It also contains a simple X10Server, which listens for network commands to be forwarded to the module.

Dependents:   X10Svr

X10.cpp

Committer:
WiredHome
Date:
2015-07-07
Revision:
1:4006f1419bc2
Parent:
0:12ed8bd4909a

File content as of revision 1:4006f1419bc2:


#include "X10.h"


//#define DEBUG "x10 "
#ifdef WIN32
#define LF "\n"
#else // mbed
#define LF "\r\n"
#endif // WIN32
#include <cstdio>
#if (defined(DEBUG) && !defined(TARGET_LPC11U24))
#define DBG(x, ...)  std::printf("[DBG %s %3d] " x LF, DEBUG, __LINE__, ##__VA_ARGS__)
#define WARN(x, ...) std::printf("[WRN %s %3d] " x LF, DEBUG, __LINE__, ##__VA_ARGS__)
#define ERR(x, ...)  std::printf("[ERR %s %3d] " x LF, DEBUG, __LINE__, ##__VA_ARGS__)
#define INFO(x, ...) std::printf("[INF %s %3d] " x LF, DEBUG, __LINE__, ##__VA_ARGS__)
#else
#define DBG(x, ...)
#define WARN(x, ...)
#define ERR(x, ...)
#define INFO(x, ...)
#endif

#define CMD_MASK  0xFF00
#define CMD_DELAY 0x0100


/* The house code is the high nibble of the command data. */
/* 1111 0000 0000 0000  House Mask */
/* 0000 0100 1101 1000  Device Mask */
/* 0000 0000 1011 1000  Command Mask */
/* 1111 0101 1111 1000  Composite Mask */
const uint16_t house_codes[16] = {
    /* 1111 0000 0000 0000  House Mask */
    0x6000,         /* 0110 0000 0000 0000  A */
    0x7000,         /* 0111 0000 0000 0000  B */
    0x4000,         /* 0100 0000 0000 0000  C */
    0x5000,         /* 0101 0000 0000 0000  D */
    0x8000,         /* 1000 0000 0000 0000  E */
    0x9000,         /* 1001 0000 0000 0000  F */
    0xA000,         /* 1010 0000 0000 0000  G */
    0xB000,         /* 1011 0000 0000 0000  H */
    0xE000,         /* 1110 0000 0000 0000  I */
    0xF000,         /* 1111 0000 0000 0000  J */
    0xC000,         /* 1100 0000 0000 0000  K */
    0xD000,         /* 1101 0000 0000 0000  L */
    0x0000,         /* 0000 0000 0000 0000  M */
    0x1000,         /* 0001 0000 0000 0000  N */
    0x2000,         /* 0010 0000 0000 0000  O */
    0x3000,         /* 0011 0000 0000 0000  P */
};

/* The device codes are described by bits 3, 4, 6, and 10. */
#define DEVICE_BITS 0x0458
const uint16_t device_codes[16] = {
    /* 0000 0100 0101 1000  Device Mask */
    0x0000,         /* 0000 0000 0000 0000  1 */
    0x0010,         /* 0000 0000 0001 0000  2 */
    0x0008,         /* 0000 0000 0000 1000  3 */
    0x0018,         /* 0000 0000 0001 1000  4 */
    0x0040,         /* 0000 0000 0100 0000  5 */
    0x0050,         /* 0000 0000 0101 0000  6 */
    0x0048,         /* 0000 0000 0100 1000  7 */
    0x0058,         /* 0000 0000 0101 1000  8 */
    0x0400,         /* 0000 0100 0000 0000  9 */
    0x0410,         /* 0000 0100 0001 0000  10 */
    0x0408,         /* 0000 0100 0000 1000  11 */
    0x0418,         /* 0000 0100 0001 1000  12 */
    0x0440,         /* 0000 0100 0100 0000  13 */
    0x0450,         /* 0000 0100 0101 0000  14 */
    0x0448,         /* 0000 0100 0100 1000  15 */
    0x0458,         /* 0000 0100 0101 1000  16 */
};


/* The command codes are described by bits 1, 2, 5, 7, 8, 9, and 11. */
const uint16_t command_codes[] = {
    /* 0000 0000 1011 1000  Command Mask */
    0x0000,         /* 0000 0000 0000 0000  ON */
    0x0020,         /* 0000 0000 0010 0000  OFF */
    0x0088,         /* 0000 0000 1000 1000  BRIGHT */
    0x0098,         /* 0000 0000 1001 1000  DIM */
};

const unsigned char header[] = { 0xD5, 0xAA };
const unsigned char footer[] = { 0xAD };

enum command_index {
    ON, OFF, BRIGHTEN, DIM
};

bool fSentOn[16][16];

X10Interface::X10Interface(PinName RTS, PinName DTR, uint8_t MultiMessageDelay_s, uint32_t BaudRate)
{
    cm17Pins = new BusOut(RTS, DTR);
    bitEnqueue = bitDequeue = 0;
    mask = 0x80;
    baud(BaudRate);
    multiMsgDelay_s = MultiMessageDelay_s;
    *cm17Pins = RESET;
    wait_ms(200);
    *cm17Pins = STANDBY;
    
}


X10Interface::ERR_EXIT X10Interface::baud(uint32_t baudrate)
{
    bitperiod_us = 1000000/baudrate/2;
    return ERR_SUCCESS;
}

X10Interface::ERR_EXIT X10Interface::delay(uint8_t MultiMessageDelay_s)
{
    multiMsgDelay_s = MultiMessageDelay_s;
    return ERR_SUCCESS;
}

X10Interface::ERR_EXIT X10Interface::ParseCommand(char * message)
{
    const char seps[]   = " ,\t\n";
    char *token;
    int argc = 0;
    char *argv[20];

    strcpy(commandBuffer, message);
    INFO("ParseCommand(%s)", commandBuffer);
    // tokenize into individual items
    token = strtok(commandBuffer, seps);
    while( token != NULL ) {
        argv[argc++] = token;
        token = strtok( NULL, seps );
        INFO("argv[%d] = %s", argc-1, argv[argc-1]);
    }
    for (int i = 0; i < argc; i++) {
        if (strchr("ABCDEFGHIJKLMNOPabcdefghijklmnop", *argv[i]) && (i + 1) < argc)  {
            ERR_EXIT z;  // return # args to increment
            z = DoCommand(argv[i], argv[i + 1]);
            if (z == ERR_SUCCESS)
                i += 1;
        } else if (*argv[i] == '/') {
            // direct hex code with /XXXX
            unsigned int command_word;
            
            sscanf(argv[i],"/%X", &command_word);
            if (cm17a_CmdSend(command_word)) {
                return ERR_BADARGS;
            }
            wait_ms(50);    // Wait between commands
        } else if (*argv[i] >= '0' && *argv[i] <= '9') {
            // change bit period
            uint32_t b = atol(argv[i]);
            INFO("set baud %s => %d", argv[i], b);
            baud(b);
        } else {
            ERR("Can't parse %s\n", argv[i]);
            return ERR_BADARGS;
        }
        if (i +1 < argc) {
            enqueueData(CMD_DELAY | multiMsgDelay_s);   // 0x01:sec as a 16-bit value
        }
    }
    return ERR_SUCCESS;
}


uint16_t X10Interface::cm17a_CmdLookup(int iHouse, int iUnit, enum command_index command)
{
    uint16_t retval;

    switch (command) {
        case BRIGHTEN:
        case DIM:
            retval = house_codes[iHouse] | command_codes[command];
            break;
        default:
            retval = (house_codes[iHouse] | device_codes[iUnit] | command_codes[command]);
            break;
    }
    return retval;
}


X10Interface::ERR_EXIT X10Interface::cm17a_CmdSend(uint16_t command)
{
    INFO("cm17a_CmdSend(%04X)", command);
    if (cm17a_Header()
            || enqueueData((unsigned char)(command >> 8))
            || enqueueData((unsigned char)command)
            || cm17a_Footer()) {
        return ERR_COMERR;
    }
    return ERR_SUCCESS;
}


X10Interface::ERR_EXIT X10Interface::cm17a_CmdSend(int iHouse, int iUnit, enum command_index command)
{
    uint16_t command_word;

    command_word = cm17a_CmdLookup(iHouse, iUnit, command);
    return cm17a_CmdSend(command_word);    
}


X10Interface::ERR_EXIT X10Interface::cm17a_Header(void)
{
    return write_stream(header, sizeof(header));
}


X10Interface::ERR_EXIT X10Interface::enqueueData(uint16_t word)
{
    if (bitEnqueue < sizeof(bitBuffer)) {
        bitBuffer[bitEnqueue++] = word;
        //for (int i=0; i<bitEnqueue; i++)
        //    INFO("%2d: %04X", i, bitBuffer[i]);
        return ERR_SUCCESS;
    } else {
        ERR("bitBuffer is full, can't accept more");
        return ERR_COMERR;
    }
}


X10Interface::ERR_EXIT X10Interface::cm17a_Footer(void)
{
    X10Interface::ERR_EXIT i;

    i = write_stream(footer, sizeof(footer));
    ticker.attach_us(this, &X10Interface::x10stream, bitperiod_us/2);
    return i;
}




void X10Interface::x10stream(void)
{
    uint16_t msg = bitBuffer[bitDequeue];
    static bool timerIsRunning = false;
    
    if (msg & CMD_MASK) {
        if (*cm17Pins != STANDBY)
            *cm17Pins = STANDBY;
        // msg & CMD_DELAY, but this is the only possible command
        // if (msg & CMD_MASK == CMD_DELAY) ...
        // Delay, the low byte number of seconds
        if (!timerIsRunning) {
            timer.reset();
            timer.start();
            timerIsRunning = true;
        } else {
            if (timer.read_ms() >= 1000 * (msg & 0xFF)) {
                // timed out, so advance past this entry
                timer.stop();
                timerIsRunning = false;
                bitDequeue++;
                if (bitDequeue >= bitEnqueue) {
                    INFO("detach");
                    ticker.detach();
                    bitEnqueue = bitDequeue = 0;
                    mask = 0x80;
                    return; //false;
                }        
            }
        }
        return;
    } else {
        // bitstream
        enum SIGNAL signal = (msg & mask) ? HIGH : LOW;
        if (*cm17Pins != STANDBY) {
            *cm17Pins = STANDBY;
            if (bitDequeue >= bitEnqueue) {
                ticker.detach();
                bitEnqueue = bitDequeue = 0;
                mask = 0x80;
                return; //false;
            }
            return; //true;
        }
        *cm17Pins = signal;
        mask >>= 1;
        if (mask == 0) {
            mask = 0x80;
            bitDequeue++;
        }
    }
    return; //true;
}


X10Interface::ERR_EXIT X10Interface::write_stream(const unsigned char * data, size_t byte_count)
{
    size_t i;
    ERR_EXIT ret = ERR_SUCCESS;

    for (i = 0; i < byte_count; i++) {
        ret = enqueueData(data[i]);
        if (ret)
            break;
    }
    return ret;
}


char toupper(char x)
{
    if (x >= 'a')
        x -= ('a' - 'A');
    return x;
}


X10Interface::ERR_EXIT X10Interface::parse_device(const char* szDevice, int* iHouse, int* iUnit)
{
    *iHouse = -1;
    *iUnit = -1;

    if (strlen(szDevice) >= 2) {
        *iHouse = toupper(szDevice[0]) - 'A';
        *iUnit = atoi(&szDevice[1]) - 1;
    }
    if (*iHouse < 0 || *iHouse > 15 || *iUnit < 0 || *iUnit > 15) {
        ERR("Invalid house/device specification %s\n",szDevice);
        return ERR_NODESPEC;
    }
    return ERR_SUCCESS;
}


/* DoCommand
 *
 *  <house><unit> [+|-]0|1|2|3|4|5|6
 */
X10Interface::ERR_EXIT X10Interface::DoCommand(const char* szDevice, const char* szOp)
{
    int iHouse;
    int iUnit;
    int steps;

    if (parse_device(szDevice, &iHouse, &iUnit))
        return ERR_BADCMD;

    if (*szOp == '+' || *szOp == '-') {
        // Increment up or down # times
        steps = abs(atoi(szOp));
        if (steps < 1 || steps > 6) {
            ERR("Invalid steps (%d).  Must be [1..6].\n", steps);
            return ERR_BADSTEP;
        }

        /* Turn the device we wish to control on first.  If we don't
           do this, the dim command gets handled by the last device
           that got turned on.  The cm17a dim/brighten command doesn't
           use a device code. */
        if (!fSentOn[iHouse][iUnit]) {
            if (cm17a_CmdSend(iHouse, iUnit, ON) != 0) {
                ERR("Command failed.\n");
                return ERR_BADCMD;
            } else
                fSentOn[iHouse][iUnit] = 1;
        }
        do {
            if (cm17a_CmdSend(iHouse, iUnit, (*szOp == '+') ? BRIGHTEN : DIM))
                ERR("Command failed.\n");
        } while (--steps > 0);
    } else if (*szOp == '1') {
        if (cm17a_CmdSend(iHouse, iUnit, ON) != 0)
            ERR("Command failed.\n");
        else
            fSentOn[iHouse][iUnit] = 1;
    } else if (*szOp == '0') {
        if (cm17a_CmdSend(iHouse, iUnit, OFF) != 0)
            ERR("Command failed.\n");
        else
            fSentOn[iHouse][iUnit] = 0;
    } else {
        /* Could be leadin to multiple unit command */
        if (!fSentOn[iHouse][iUnit]) {
            if (cm17a_CmdSend(iHouse, iUnit, ON) != 0) {
                ERR("Command failed.\n");
                return ERR_BADCMD;
            } else
                fSentOn[iHouse][iUnit] = 1;
        }
        ERR("can't parse [%s]\n", szDevice);
        return ERR_BADCMD;
    }
    return ERR_SUCCESS;
}