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.
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 }
Generated on Mon Jul 25 2022 09:04:25 by 1.7.2