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
- 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; }