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

Revision:
0:12ed8bd4909a
Child:
1:4006f1419bc2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/X10.cpp	Sat Jun 28 19:45:20 2014 +0000
@@ -0,0 +1,392 @@
+
+#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_us(1000);    // Wait 1 sec 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);
+        }
+    }
+    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;
+}