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.
Diff: X10.cpp
- 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; +}