/* * This is a driver for the Insteon USB powerline controller V2. * * "Insteon" is a registered trademark. It is appropriate to use "Insteon" * to refer to products that are licensed to use that brand by SmartLabs, * such as the Insteon devices sold by Smarthome. You should use another * name to refer to Open Source software. My own software for driving * Insteon devices is called "Ion". * * Written by Bruce Perens . * Copyright (C) 2006 Sourcelabs. * * This is Free Software under the GNU General Public License version * 2.1 or any later version. If you wish another license, please write * to the author. * * Sourcelabs pays me to spend half of my work time on Open Source * projects of my choice. Thus, they sponsored this development. * * Currently, this software writes the ROM version command and dumps the * result, then monitors the PLC output until interrupted. * * Obviously, I have more plans for it. - Bruce */ #include #include #include #include #include #define ION_READ_BUFFER_SIZE 8 #define ION_WRITE_BUFFER_SIZE 1024 struct _IonPLC { char * r_head; char * r_tail; char * w_head; char * w_tail; HIDInterface * hid; unsigned int got_ack:1; char r_data[ION_READ_BUFFER_SIZE]; char w_data[ION_WRITE_BUFFER_SIZE]; }; typedef struct _IonPLC * IonPLC; /* * ReceiveCount is a table of the length of fixed-length packets. * For variable-length packets, it specifies the length of the header data. * * Labels for PLC packet bytes: * * A2 Address (high byte, then low byte). * ACK Acknowledge (0x06) or Negative Acknowledge (0x15). * AND AND mask. NOT Bits to clear. * C2 Checksum (high byte, then low byte). * D2 Device type (high byte, then low byte). * EN Event number. * FR Firmware revision number. * I3 Insteon address (three bytes, high first). * L Length (one byte). * L2 Length (high byte, then low byte). * MF Message flags. * OR OR mask. Bits to set. * TV Timer value. * XT X10 type: 0 for address, 1 for data. * dN Data bytes. N indicates how many. */ static const int ReceiveCount[] = { 5, /* 0x40 Write to IBIOS: A2 L2 ACK */ 1, /* 0x41 IBIOS message: L dL. */ 7, /* 0x42 Read from IBIOS: A2 L2 C2 ACK */ 0, /* 0x43 ETX-terminated IBIOS message: d(variable) ETX (3) */ 7, /* 0x44 Get Checksim: A2 L2 C2 ACK */ 1, /* 0x45 Event report: EN */ 5, /* 0x46 Mask (bit-manipulation): A2 OR AND ACK */ 3, /* 0x47 Simiulated event: EN TV ACK */ 7, /* 0x48 Get version: I3 D2 FR */ 2, /* 0x49 Debug reporting next instruction to execute: A2 */ 2, /* 0x4a X10 recieved: XT d1 */ -1, /* 0x4b */ -1, /* 0x4c */ -1, /* 0x4d */ -1, /* 0x4e */ 10, /* 0x4f Insteon packet: EN I3(source) I3(dest) MF d2 [d14] ACK*/ }; static const char * const EventExplanations[] = { "SALad initialized", /* 0 */ "Received the first message in a hop sequence addressed to me", /* 1 */ "Received the first message in a hop sequence not addressed to me",/*2*/ "Received a duplicate message", /* 3 */ "Recieved an ACK to my direct message", /* 4 */ "Did not get an ACK to my direct message, even after 5 retries",/* 5 */ "Received a message to an unknown address, and censored it", /* 6 */ "Received a response to my join-me message", /* 7 */ "Received an X10 byte", /* 8 */ "Received an X10 extended-message byte", /* 9 */ "A SET button tap sequence has begun", /* a */ "The SET button is being pressed", /* b */ "The SET button has been released", /* c */ "The tick counter has expired", /* d */ "An alarm tripped", /* e */ "The dedicated midnight alarm tripped", /* f */ "The dedicated 2:00 AM alarm tripped", /* 10 */ "Received a serial byte for SALad processing", /* 11 */ "Received an unknown IBIOS serial command", /* 12 */ "Received an interrupt from my daughter card", /* 13 */ "The load was turned on", /* 14 */ "The load was turned off", /* 15 */ }; static int fill(IonPLC b) { hid_return r; r = hid_interrupt_read(b->hid, USB_ENDPOINT_IN+1, (char *)b->r_data, ION_READ_BUFFER_SIZE, 100); if ( r == HID_RET_SUCCESS ) { unsigned char length = b->r_data[0] & 0x0f; if ( b->r_data[0] & 0x80 ) { printf("Got CTS\n"); } if ( length > 7 ) { fprintf(stderr, "Invalid PLC message length %d.\n", length); return 0; } b->r_tail = (b->r_head = b->r_data + 1) + length; return (int)length; } else return 0; } inline static int getByte(IonPLC b) { int c; if ( b->r_head == b->r_tail && fill(b) == 0 ) return -1; c = *(b->r_head)++ & 0xff; return c; } void IonPLCClose(IonPLC b) { hid_close(b->hid); hid_delete_HIDInterface(&(b->hid)); hid_cleanup(); free(b); } inline static int getBytes(IonPLC b, unsigned char * r_data, int length, int * copied, int real_length) { int i; int c; int cp = *copied; for ( i = 0; i < real_length; i++ ) { if ( (c = getByte(b)) < 0 ) return 0; if ( cp < length ) r_data[cp++] = c & 0xff; } *copied = cp; return cp; } int IonPLCRead(IonPLC b, unsigned char * data, int length) { int c; int copied = 0; while ( (c = getByte(b)) >= 0 && c != 2 ) { fprintf(stderr, "Discarding unsynchronized byte %02x\n", c); } if ( c < 0 ) return 0; c = getByte(b); if ( c >= 0x40 && c <= 0x4f ) { int get = ReceiveCount[c & 0x0f]; if ( copied < length ) data[0] = c & 0xff; if ( get > 0 ) getBytes(b, &(data[1]), length - copied, &copied, get); else { fprintf(stderr, "Don't know what to do with command %x\n", c); return copied; } switch ( c ) { case 0x41: get = data[1] & 0xff; break; case 0x42: get = ((data[3] & 0xff) << 8) | (data[4] & 0xff); break; case 0x4f: if ( data[8] & 0x10 ) { /* Extended message */ fprintf(stderr, "Got extended message.\n"); get = 14; break; } /* Fall through */ default: return copied; } getBytes(b, &data[copied], length - copied, &copied, get); } else if ( c >= 0 ) { fprintf(stderr, "Don't know what to do with received command %02x.\n", c); } return 0; } IonPLC IonPLCOpenUSB() { static const HIDInterfaceMatcher matcher = { 0x10bf, 0x4, NULL, NULL, 0 }; hid_return r; IonPLC b = malloc(sizeof(struct _IonPLC)); if ( b == 0 || hid_init() != HID_RET_SUCCESS || (b->hid = hid_new_HIDInterface()) == 0 ) { fprintf(stderr, "Failed to initialize libhid.\n"); return 0; } b->r_head = b->r_tail = b->r_data; b->w_head = b->w_tail = b->w_data; if ( (r = hid_force_open(b->hid, 0, &matcher, 3)) != HID_RET_SUCCESS ) { fprintf(stderr, "hid_force_open failed with return code %d\n", r); return 0; } return b; } int main(void) { IonPLC b = IonPLCOpenUSB(); hid_return r; unsigned char msg[] = { 0x02, 0x02, 0x48 }; /* Get ROM version */ char empty = 0; int written = 0; if ( b == 0 ) return 1; /* * For some reason, the first write just disappears. * I don't know if this is a libhid or PLC issue. * So, I just do an empty write here. */ hid_interrupt_write(b->hid, USB_ENDPOINT_OUT+1, &empty, 0, 100); for ( ; ; ) { unsigned char p[1024]; int length; unsigned char event; if ( (length = IonPLCRead(b, p, sizeof(p))) > 0 ) { switch (p[0]) { case 0x45: event = p[1]; printf("Event %x", event); if ( event < (sizeof(EventExplanations)/sizeof(*EventExplanations)) ) printf(": %s", EventExplanations[event]); printf("\n"); break; case 0x48: printf("PLC address %x.%x.%x, Decice type %x subtype %x, Firmware version %x.\n" ,p[1], p[2] ,p[3] ,p[4] ,p[5] ,p[6] ,p[7]); break; case 0x4f: printf("Insteon Message: Event %x, %x.%x.%x %x.%x.%x %x %x %x.\n" ,p[1] ,p[2] ,p[3] ,p[4] ,p[5] ,p[6] ,p[7] ,p[8] ,p[9] ,p[10]); break; default: printf("Got command %x.\n", p[0]); } fflush(stdout); } if ( written < 1 ) { r = hid_interrupt_write(b->hid, USB_ENDPOINT_OUT+1, (char *)msg, sizeof(msg), 100); if ( r != 0 ) fprintf(stderr, "Write returned %d.\n", r); written++; } } IonPLCClose(b); return 0; }