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

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers X10.cpp Source File

X10.cpp

00001 
00002 #include "X10.h"
00003 
00004 
00005 //#define DEBUG "x10 "
00006 #ifdef WIN32
00007 #define LF "\n"
00008 #else // mbed
00009 #define LF "\r\n"
00010 #endif // WIN32
00011 #include <cstdio>
00012 #if (defined(DEBUG) && !defined(TARGET_LPC11U24))
00013 #define DBG(x, ...)  std::printf("[DBG %s %3d] " x LF, DEBUG, __LINE__, ##__VA_ARGS__)
00014 #define WARN(x, ...) std::printf("[WRN %s %3d] " x LF, DEBUG, __LINE__, ##__VA_ARGS__)
00015 #define ERR(x, ...)  std::printf("[ERR %s %3d] " x LF, DEBUG, __LINE__, ##__VA_ARGS__)
00016 #define INFO(x, ...) std::printf("[INF %s %3d] " x LF, DEBUG, __LINE__, ##__VA_ARGS__)
00017 #else
00018 #define DBG(x, ...)
00019 #define WARN(x, ...)
00020 #define ERR(x, ...)
00021 #define INFO(x, ...)
00022 #endif
00023 
00024 #define CMD_MASK  0xFF00
00025 #define CMD_DELAY 0x0100
00026 
00027 
00028 /* The house code is the high nibble of the command data. */
00029 /* 1111 0000 0000 0000  House Mask */
00030 /* 0000 0100 1101 1000  Device Mask */
00031 /* 0000 0000 1011 1000  Command Mask */
00032 /* 1111 0101 1111 1000  Composite Mask */
00033 const uint16_t house_codes[16] = {
00034     /* 1111 0000 0000 0000  House Mask */
00035     0x6000,         /* 0110 0000 0000 0000  A */
00036     0x7000,         /* 0111 0000 0000 0000  B */
00037     0x4000,         /* 0100 0000 0000 0000  C */
00038     0x5000,         /* 0101 0000 0000 0000  D */
00039     0x8000,         /* 1000 0000 0000 0000  E */
00040     0x9000,         /* 1001 0000 0000 0000  F */
00041     0xA000,         /* 1010 0000 0000 0000  G */
00042     0xB000,         /* 1011 0000 0000 0000  H */
00043     0xE000,         /* 1110 0000 0000 0000  I */
00044     0xF000,         /* 1111 0000 0000 0000  J */
00045     0xC000,         /* 1100 0000 0000 0000  K */
00046     0xD000,         /* 1101 0000 0000 0000  L */
00047     0x0000,         /* 0000 0000 0000 0000  M */
00048     0x1000,         /* 0001 0000 0000 0000  N */
00049     0x2000,         /* 0010 0000 0000 0000  O */
00050     0x3000,         /* 0011 0000 0000 0000  P */
00051 };
00052 
00053 /* The device codes are described by bits 3, 4, 6, and 10. */
00054 #define DEVICE_BITS 0x0458
00055 const uint16_t device_codes[16] = {
00056     /* 0000 0100 0101 1000  Device Mask */
00057     0x0000,         /* 0000 0000 0000 0000  1 */
00058     0x0010,         /* 0000 0000 0001 0000  2 */
00059     0x0008,         /* 0000 0000 0000 1000  3 */
00060     0x0018,         /* 0000 0000 0001 1000  4 */
00061     0x0040,         /* 0000 0000 0100 0000  5 */
00062     0x0050,         /* 0000 0000 0101 0000  6 */
00063     0x0048,         /* 0000 0000 0100 1000  7 */
00064     0x0058,         /* 0000 0000 0101 1000  8 */
00065     0x0400,         /* 0000 0100 0000 0000  9 */
00066     0x0410,         /* 0000 0100 0001 0000  10 */
00067     0x0408,         /* 0000 0100 0000 1000  11 */
00068     0x0418,         /* 0000 0100 0001 1000  12 */
00069     0x0440,         /* 0000 0100 0100 0000  13 */
00070     0x0450,         /* 0000 0100 0101 0000  14 */
00071     0x0448,         /* 0000 0100 0100 1000  15 */
00072     0x0458,         /* 0000 0100 0101 1000  16 */
00073 };
00074 
00075 
00076 /* The command codes are described by bits 1, 2, 5, 7, 8, 9, and 11. */
00077 const uint16_t command_codes[] = {
00078     /* 0000 0000 1011 1000  Command Mask */
00079     0x0000,         /* 0000 0000 0000 0000  ON */
00080     0x0020,         /* 0000 0000 0010 0000  OFF */
00081     0x0088,         /* 0000 0000 1000 1000  BRIGHT */
00082     0x0098,         /* 0000 0000 1001 1000  DIM */
00083 };
00084 
00085 const unsigned char header[] = { 0xD5, 0xAA };
00086 const unsigned char footer[] = { 0xAD };
00087 
00088 enum command_index {
00089     ON, OFF, BRIGHTEN, DIM
00090 };
00091 
00092 bool fSentOn[16][16];
00093 
00094 X10Interface::X10Interface(PinName RTS, PinName DTR, uint8_t MultiMessageDelay_s, uint32_t BaudRate)
00095 {
00096     cm17Pins = new BusOut(RTS, DTR);
00097     bitEnqueue = bitDequeue = 0;
00098     mask = 0x80;
00099     baud(BaudRate);
00100     multiMsgDelay_s = MultiMessageDelay_s;
00101     *cm17Pins = RESET;
00102     wait_ms(200);
00103     *cm17Pins = STANDBY;
00104     
00105 }
00106 
00107 
00108 X10Interface::ERR_EXIT X10Interface::baud(uint32_t baudrate)
00109 {
00110     bitperiod_us = 1000000/baudrate/2;
00111     return ERR_SUCCESS;
00112 }
00113 
00114 X10Interface::ERR_EXIT X10Interface::delay(uint8_t MultiMessageDelay_s)
00115 {
00116     multiMsgDelay_s = MultiMessageDelay_s;
00117     return ERR_SUCCESS;
00118 }
00119 
00120 X10Interface::ERR_EXIT X10Interface::ParseCommand(char * message)
00121 {
00122     const char seps[]   = " ,\t\n";
00123     char *token;
00124     int argc = 0;
00125     char *argv[20];
00126 
00127     strcpy(commandBuffer, message);
00128     INFO("ParseCommand(%s)", commandBuffer);
00129     // tokenize into individual items
00130     token = strtok(commandBuffer, seps);
00131     while( token != NULL ) {
00132         argv[argc++] = token;
00133         token = strtok( NULL, seps );
00134         INFO("argv[%d] = %s", argc-1, argv[argc-1]);
00135     }
00136     for (int i = 0; i < argc; i++) {
00137         if (strchr("ABCDEFGHIJKLMNOPabcdefghijklmnop", *argv[i]) && (i + 1) < argc)  {
00138             ERR_EXIT z;  // return # args to increment
00139             z = DoCommand(argv[i], argv[i + 1]);
00140             if (z == ERR_SUCCESS)
00141                 i += 1;
00142         } else if (*argv[i] == '/') {
00143             // direct hex code with /XXXX
00144             unsigned int command_word;
00145             
00146             sscanf(argv[i],"/%X", &command_word);
00147             if (cm17a_CmdSend(command_word)) {
00148                 return ERR_BADARGS;
00149             }
00150             wait_ms(50);    // Wait between commands
00151         } else if (*argv[i] >= '0' && *argv[i] <= '9') {
00152             // change bit period
00153             uint32_t b = atol(argv[i]);
00154             INFO("set baud %s => %d", argv[i], b);
00155             baud(b);
00156         } else {
00157             ERR("Can't parse %s\n", argv[i]);
00158             return ERR_BADARGS;
00159         }
00160         if (i +1 < argc) {
00161             enqueueData(CMD_DELAY | multiMsgDelay_s);   // 0x01:sec as a 16-bit value
00162         }
00163     }
00164     return ERR_SUCCESS;
00165 }
00166 
00167 
00168 uint16_t X10Interface::cm17a_CmdLookup(int iHouse, int iUnit, enum command_index command)
00169 {
00170     uint16_t retval;
00171 
00172     switch (command) {
00173         case BRIGHTEN:
00174         case DIM:
00175             retval = house_codes[iHouse] | command_codes[command];
00176             break;
00177         default:
00178             retval = (house_codes[iHouse] | device_codes[iUnit] | command_codes[command]);
00179             break;
00180     }
00181     return retval;
00182 }
00183 
00184 
00185 X10Interface::ERR_EXIT X10Interface::cm17a_CmdSend(uint16_t command)
00186 {
00187     INFO("cm17a_CmdSend(%04X)", command);
00188     if (cm17a_Header()
00189             || enqueueData((unsigned char)(command >> 8))
00190             || enqueueData((unsigned char)command)
00191             || cm17a_Footer()) {
00192         return ERR_COMERR;
00193     }
00194     return ERR_SUCCESS;
00195 }
00196 
00197 
00198 X10Interface::ERR_EXIT X10Interface::cm17a_CmdSend(int iHouse, int iUnit, enum command_index command)
00199 {
00200     uint16_t command_word;
00201 
00202     command_word = cm17a_CmdLookup(iHouse, iUnit, command);
00203     return cm17a_CmdSend(command_word);    
00204 }
00205 
00206 
00207 X10Interface::ERR_EXIT X10Interface::cm17a_Header(void)
00208 {
00209     return write_stream(header, sizeof(header));
00210 }
00211 
00212 
00213 X10Interface::ERR_EXIT X10Interface::enqueueData(uint16_t word)
00214 {
00215     if (bitEnqueue < sizeof(bitBuffer)) {
00216         bitBuffer[bitEnqueue++] = word;
00217         //for (int i=0; i<bitEnqueue; i++)
00218         //    INFO("%2d: %04X", i, bitBuffer[i]);
00219         return ERR_SUCCESS;
00220     } else {
00221         ERR("bitBuffer is full, can't accept more");
00222         return ERR_COMERR;
00223     }
00224 }
00225 
00226 
00227 X10Interface::ERR_EXIT X10Interface::cm17a_Footer(void)
00228 {
00229     X10Interface::ERR_EXIT i;
00230 
00231     i = write_stream(footer, sizeof(footer));
00232     ticker.attach_us(this, &X10Interface::x10stream, bitperiod_us/2);
00233     return i;
00234 }
00235 
00236 
00237 
00238 
00239 void X10Interface::x10stream(void)
00240 {
00241     uint16_t msg = bitBuffer[bitDequeue];
00242     static bool timerIsRunning = false;
00243     
00244     if (msg & CMD_MASK) {
00245         if (*cm17Pins != STANDBY)
00246             *cm17Pins = STANDBY;
00247         // msg & CMD_DELAY, but this is the only possible command
00248         // if (msg & CMD_MASK == CMD_DELAY) ...
00249         // Delay, the low byte number of seconds
00250         if (!timerIsRunning) {
00251             timer.reset();
00252             timer.start();
00253             timerIsRunning = true;
00254         } else {
00255             if (timer.read_ms() >= 1000 * (msg & 0xFF)) {
00256                 // timed out, so advance past this entry
00257                 timer.stop();
00258                 timerIsRunning = false;
00259                 bitDequeue++;
00260                 if (bitDequeue >= bitEnqueue) {
00261                     INFO("detach");
00262                     ticker.detach();
00263                     bitEnqueue = bitDequeue = 0;
00264                     mask = 0x80;
00265                     return; //false;
00266                 }        
00267             }
00268         }
00269         return;
00270     } else {
00271         // bitstream
00272         enum SIGNAL signal = (msg & mask) ? HIGH : LOW;
00273         if (*cm17Pins != STANDBY) {
00274             *cm17Pins = STANDBY;
00275             if (bitDequeue >= bitEnqueue) {
00276                 ticker.detach();
00277                 bitEnqueue = bitDequeue = 0;
00278                 mask = 0x80;
00279                 return; //false;
00280             }
00281             return; //true;
00282         }
00283         *cm17Pins = signal;
00284         mask >>= 1;
00285         if (mask == 0) {
00286             mask = 0x80;
00287             bitDequeue++;
00288         }
00289     }
00290     return; //true;
00291 }
00292 
00293 
00294 X10Interface::ERR_EXIT X10Interface::write_stream(const unsigned char * data, size_t byte_count)
00295 {
00296     size_t i;
00297     ERR_EXIT ret = ERR_SUCCESS;
00298 
00299     for (i = 0; i < byte_count; i++) {
00300         ret = enqueueData(data[i]);
00301         if (ret)
00302             break;
00303     }
00304     return ret;
00305 }
00306 
00307 
00308 char toupper(char x)
00309 {
00310     if (x >= 'a')
00311         x -= ('a' - 'A');
00312     return x;
00313 }
00314 
00315 
00316 X10Interface::ERR_EXIT X10Interface::parse_device(const char* szDevice, int* iHouse, int* iUnit)
00317 {
00318     *iHouse = -1;
00319     *iUnit = -1;
00320 
00321     if (strlen(szDevice) >= 2) {
00322         *iHouse = toupper(szDevice[0]) - 'A';
00323         *iUnit = atoi(&szDevice[1]) - 1;
00324     }
00325     if (*iHouse < 0 || *iHouse > 15 || *iUnit < 0 || *iUnit > 15) {
00326         ERR("Invalid house/device specification %s\n",szDevice);
00327         return ERR_NODESPEC;
00328     }
00329     return ERR_SUCCESS;
00330 }
00331 
00332 
00333 /* DoCommand
00334  *
00335  *  <house><unit> [+|-]0|1|2|3|4|5|6
00336  */
00337 X10Interface::ERR_EXIT X10Interface::DoCommand(const char* szDevice, const char* szOp)
00338 {
00339     int iHouse;
00340     int iUnit;
00341     int steps;
00342 
00343     if (parse_device(szDevice, &iHouse, &iUnit))
00344         return ERR_BADCMD;
00345 
00346     if (*szOp == '+' || *szOp == '-') {
00347         // Increment up or down # times
00348         steps = abs(atoi(szOp));
00349         if (steps < 1 || steps > 6) {
00350             ERR("Invalid steps (%d).  Must be [1..6].\n", steps);
00351             return ERR_BADSTEP;
00352         }
00353 
00354         /* Turn the device we wish to control on first.  If we don't
00355            do this, the dim command gets handled by the last device
00356            that got turned on.  The cm17a dim/brighten command doesn't
00357            use a device code. */
00358         if (!fSentOn[iHouse][iUnit]) {
00359             if (cm17a_CmdSend(iHouse, iUnit, ON) != 0) {
00360                 ERR("Command failed.\n");
00361                 return ERR_BADCMD;
00362             } else
00363                 fSentOn[iHouse][iUnit] = 1;
00364         }
00365         do {
00366             if (cm17a_CmdSend(iHouse, iUnit, (*szOp == '+') ? BRIGHTEN : DIM))
00367                 ERR("Command failed.\n");
00368         } while (--steps > 0);
00369     } else if (*szOp == '1') {
00370         if (cm17a_CmdSend(iHouse, iUnit, ON) != 0)
00371             ERR("Command failed.\n");
00372         else
00373             fSentOn[iHouse][iUnit] = 1;
00374     } else if (*szOp == '0') {
00375         if (cm17a_CmdSend(iHouse, iUnit, OFF) != 0)
00376             ERR("Command failed.\n");
00377         else
00378             fSentOn[iHouse][iUnit] = 0;
00379     } else {
00380         /* Could be leadin to multiple unit command */
00381         if (!fSentOn[iHouse][iUnit]) {
00382             if (cm17a_CmdSend(iHouse, iUnit, ON) != 0) {
00383                 ERR("Command failed.\n");
00384                 return ERR_BADCMD;
00385             } else
00386                 fSentOn[iHouse][iUnit] = 1;
00387         }
00388         ERR("can't parse [%s]\n", szDevice);
00389         return ERR_BADCMD;
00390     }
00391     return ERR_SUCCESS;
00392 }