/**************************************************************************** ** rfcmd.c *********************************************************** **************************************************************************** * * rfcmd - utility to control NEXA and other RF remote receivers through a TellStick * USB interface * * Copyright (C) 2007 Tord Andersson * * License: GPL v. 2 * * Authors: * Tord Andersson * Micke Prag * Gudmund Berggren * * Tapani Rintala / userspace libusb / libftdi - version 02-2009 */ /******************************************************************************* * Modifications from rfcmd.c ver 0.2 done by Gudmund * Added support for IKEA * Note: * 1. System code * 2. Level 0 == Off (For dimmers and non dimmers) * Level 1== 10%. (for dimmers) * .... * Level 9 == 90 % (for dimmers) * Level 10 == 100 % (or ON for non dimmers) * 3. Command line syntax: * /usr/local/bin/rfcmd /dev/ttyUSB0 IKEA 0 1 10 1" * Arg 1: device * Arg 2: Protocoll * Arg 3: System code * Arg 4: Device code * Arg 5: Level (0=off, 1 = 10%, 2=20%,...9=90%, 10=100%) * Arg 6: DimStyle (0=instant change, 1=gradually change) ******************************************************************************/ /******************************************************************************* * Modifications to rfcmd.c ver 2.1.0 done by Tord Andersson * Introduced semaphore protection to avoid problems with simultaneous port * access from several processes (typically cron jobs). Note! Need rt lib. ******************************************************************************/ /******************************************************************************* * Modifications from rfcmd.c ver 2.1.0 done by Leo * Added support for RISINGSUN * Note: * 1. Command line syntax: * /usr/local/bin/rfcmd /dev/ttyUSB0 RISINGSUN 4 1 0" * Arg 1: device * Arg 2: protocol * Arg 3: code * Arg 4: device number * Arg 5: Level (0=off, 1 = on) ******************************************************************************/ /******************************************************************************* * Modifications from rfcmd.c ver 2.1.0 based on marvelous work by Snakehand * See http://www.telldus.com/forum/viewtopic.php?t=97&start=63 * Added support for EVERFLOURISH * Note: * 1. Command line syntax: * /usr/local/bin/rfcmd /dev/ttyUSB0 EVERFLOURISH 1 15 * Arg 1: device * Arg 2: protocol * Arg 3: device number (0..65535) * Arg 4: Level (0=off, 15=on, 10=learn) ******************************************************************************/ /******************************************************************************* * Modifications from rfcmd ver 2.1.1 done by Johan Ström * Default disabled semaphores for FreeBSD. * Added status readback in ftdi.c, instead of wasting time in sleep. * * FreeBSD does not have support in the GENERIC kernel semaphore. * To enable usage of them, you'll have have the following option in your * kernel configuration: * * options P1003_1B_SEMAPHORES * * However, on FreeBSD only libftdi seems to be working (at least on my system), * since the device is not identified as a ucom device. And as we're accessing it * via libftdi, libftdi makes sure simultaneous access is impossible. ******************************************************************************/ #include #include #include #include #include #include #ifndef NO_SEMAPHORES #include #endif #define PROG_NAME "rfcmd" #define PROG_VERSION "2.1.1" /* #define RFCMD_DEBUG */ /* Local function declarations */ int createNexaString(const char * pHouseStr, const char * pChannelStr, const char * pOn_offStr, char * pTxStr, int waveman); int createSartanoString(const char * pChannelStr, const char * pOn_offStr, char * pTxStr); int createIkeaString(const char * pSystemStr, const char * pChannelStr, const char * pLevelStr, const char *pDimStyle, char * pStrReturn); int createRisingSunString(const char * pCodeStr, const char* pUnitStr, const char * pOn_offStr, char * pTxStr); int createEverFlourishString(const char* pUnitStr, const char * pLevelStr, char * pTxStr); void printUsage(void); void printVersion(void); #ifdef LIBFTDI int usbWriteFtdi(char *cmdstr); #endif int main( int argc, char **argv ) { struct termios tio; int fd = -1; #ifndef NO_SEMAPHORES sem_t * portMutex; char SEM_NAME[]= "RFCMD_SEM"; /* Semaphore for multiple access ctrl */ #endif char txStr[100]; if( (argc == 6) && (strcmp(*(argv+2), "NEXA") == 0)) { if (createNexaString(*(argv+3), *(argv+4), *(argv+5), txStr, 0) == 0) { printUsage(); exit(1); } /* else - a send cmd string was created */ } else if( (argc == 6) && (strcmp(*(argv+2), "WAVEMAN") == 0)) { if (createNexaString(*(argv+3),*(argv+4), *(argv+5), txStr, 1) == 0) { printUsage(); exit(1); } } else if( (argc == 5) && (strcmp(*(argv+2), "SARTANO") == 0)) { if (createSartanoString(*(argv+3), *(argv+4), txStr) == 0) { printUsage(); exit(1); } /* else - a send cmd string was created */ } else if ( (argc == 7) && (strcmp(*(argv+2),"IKEA")==0) ) { // System, Channel, Level, DimStyle, TXString if ( createIkeaString(*(argv+3), *(argv+4), *(argv+5), *(argv+6),txStr) == 0 ) { printUsage(); exit(1); } /* else - a send cmd string was created */ } else if ( (argc == 6) && (strcmp(*(argv+2),"RISINGSUN")==0) ) { // Code, Unit, Power if ( createRisingSunString(*(argv+3), *(argv+4), *(argv+5), txStr) == 0 ) { printUsage(); exit(1); } /* else - a send cmd string was created */ } else if ( (argc == 5) && (strcmp(*(argv+2),"EVERFLOURISH")==0) ) { // Unit, Level if ( createEverFlourishString(*(argv+3), *(argv+4), txStr) == 0 ) { printUsage(); exit(1); } /* else - a send cmd string was created */ } else if ( (argc >= 2) && (strcmp(*(argv+1),"--version")==0) ) { printVersion(); exit(1); } else { /* protocol or parameters not recognized */ printUsage(); exit(1); } #ifdef RFCMD_DEBUG printf("txStr: %s\n", txStr); #endif if(strlen(txStr) > 0) { #ifndef NO_SEMAPHORES /* create the semaphore - will reuse an existing one if it exists */ portMutex = sem_open(SEM_NAME,O_CREAT,0644,1); if( portMutex == SEM_FAILED) { fprintf(stderr, "%s - Error creating port semaphore\n", PROG_NAME); perror("Semaphore open error"); sem_unlink(SEM_NAME); exit(1); } /* lock semaphore to protect port from multiple access */ if(sem_wait(portMutex) != 0) { fprintf(stderr, "%s - Error aquiring port semaphore\n", PROG_NAME); sem_unlink(SEM_NAME); sem_close(portMutex); exit(1); } #endif if (strcmp(*(argv+1), "LIBUSB") != 0) { if( 0 > ( fd = open( *(argv+1), O_RDWR ) ) ) { #ifdef __FreeBSD__ fprintf(stderr, "%s - Error opening %s; You're on a FreeBSD system, you should probably use LIBUSB.\n", PROG_NAME, *(argv+1)); #else fprintf(stderr, "%s - Error opening %s\n", PROG_NAME, *(argv+1)); #endif #ifndef NO_SEMAPHORES if(sem_post(portMutex) != 0) { fprintf(stderr, "%s - Error releasing port semaphore\n", PROG_NAME); } sem_unlink(SEM_NAME); sem_close(portMutex); #endif exit(1); } /* adjust serial port parameters */ bzero(&tio, sizeof(tio)); /* clear struct for new port settings */ tio.c_cflag = B4800 | CS8 | CLOCAL | CREAD; /* CREAD not used yet */ tio.c_iflag = IGNPAR; tio.c_oflag = 0; tio.c_ispeed = 4800; tio.c_ospeed = 4800; tcflush(fd, TCIFLUSH); tcsetattr(fd,TCSANOW,&tio); write(fd, txStr, strlen(txStr)); sleep(1); /* one second sleep to avoid device 'choking' */ close(fd); /* Modified : Close fd to make a clean exit */ } else { #ifdef LIBFTDI usbWriteFtdi( txStr ); #else fprintf(stderr, "%s - Support for libftdi is not compiled in, please recompile rfcmd with support for libftdi\n", PROG_NAME); #endif } #ifndef NO_SEMAPHORES /* Unlock semaphore */ if (sem_post(portMutex) != 0) { fprintf(stderr, "%s - Error releasing port semaphore\n", PROG_NAME); sem_unlink(SEM_NAME); sem_close(portMutex); exit(1); } else { sem_unlink(SEM_NAME); sem_close(portMutex); } #endif } exit(0); } int createNexaString(const char * pHouseStr, const char * pChannelStr, const char * pOn_offStr, char * pTxStr, int waveman) { * pTxStr = '\0'; /* Make sure tx string is empty */ int houseCode; int channelCode; int on_offCode; int txCode = 0; const int unknownCode = 0x6; int bit; int bitmask = 0x0001; houseCode = (int)((* pHouseStr) - 65); /* House 'A'..'P' */ channelCode = atoi(pChannelStr) - 1; /* Channel 1..16 */ on_offCode = atoi(pOn_offStr); /* ON/OFF 0..1 */ #ifdef RFCMD_DEBUG printf("House: %d, channel: %d, on_off: %d\n", houseCode, channelCode, on_offCode); #endif /* check converted parameters for validity */ if((houseCode < 0) || (houseCode > 15) || // House 'A'..'P' (channelCode < 0) || (channelCode > 15) || (on_offCode < 0) || (on_offCode > 1)) { } else { /* b0..b11 txCode where 'X' will be represented by 1 for simplicity. b0 will be sent first */ txCode = houseCode; txCode |= (channelCode << 4); if (waveman && on_offCode == 0) { } else { txCode |= (unknownCode << 8); txCode |= (on_offCode << 11); } /* convert to send cmd string */ strcat(pTxStr,"S"); for(bit=0;bit<12;bit++) { if((bitmask & txCode) == 0) { /* bit timing might need further refinement */ strcat(pTxStr," ` `"); /* 320 us high, 960 us low, 320 us high, 960 us low */ /* strcat(pTxStr,"$k$k"); *//* 360 us high, 1070 us low, 360 us high, 1070 us low */ } else { /* add 'X' (floating bit) */ strcat(pTxStr," `` "); /* 320 us high, 960 us low, 960 us high, 320 us low */ /*strcat(pTxStr,"$kk$"); *//* 360 us high, 1070 us low, 1070 us high, 360 us low */ } bitmask = bitmask<<1; } /* add stop/sync bit and command termination char '+'*/ strcat(pTxStr," }+"); /* strcat(pTxStr,"$}+"); */ } #ifdef RFCMD_DEBUG printf("txCode: %04X\n", txCode); #endif return strlen(pTxStr); } int createSartanoString(const char * pChannelStr, const char * pOn_offStr, char * pTxStr) { * pTxStr = '\0'; /* Make sure tx string is empty */ int on_offCode; int bit; on_offCode = atoi(pOn_offStr); /* ON/OFF 0..1 */ #ifdef RFCMD_DEBUG printf("Channel: %s, on_off: %d\n", pChannelStr, on_offCode); #endif /* check converted parameters for validity */ if((strlen(pChannelStr) != 10) || (on_offCode < 0) || (on_offCode > 1)) { } else { strcat(pTxStr,"S"); for(bit=0;bit<=9;bit++) { if(strncmp(pChannelStr+bit, "1", 1) == 0) { //If it is a "1" strcat(pTxStr,"$k$k"); } else { strcat(pTxStr,"$kk$"); } } if (on_offCode >= 1) strcat(pTxStr,"$k$k$kk$"); //the "turn on"-code else strcat(pTxStr,"$kk$$k$k"); //the "turn off"-code /* add stop/sync bit and command termination char '+'*/ strcat(pTxStr,"$k+"); } return strlen(pTxStr); } int createIkeaString( const char * pSystemStr, const char * pChannelStr, const char * pLevelStr, const char *pDimStyle, char * pStrReturn) { *pStrReturn = '\0'; /* Make sure tx string is empty */ const char STARTCODE[] = "STTTTTTª"; const char TT[] = "TT"; const char A[] = "ª"; int systemCode = atoi(pSystemStr) - 1; /* System 1..16 */ int channelCode = atoi(pChannelStr); /* Channel 1..10 */ int Level = atoi(pLevelStr); /* off,10,20,..,90,on */ int DimStyle = atoi(pDimStyle); int intCode = 0; int checksum1 = 0; int checksum2 = 0; int intFade ; int i ; int rawChannelCode = 0; /* check converted parameters for validity */ if ( (channelCode <= 0) || (channelCode > 10) || (systemCode < 0) || (systemCode > 15) || (Level < 0) || (Level > 10) || (DimStyle < 0) || (DimStyle > 1)) { return 0; } if (channelCode == 10) { channelCode = 0; } rawChannelCode = (1<<(9-channelCode)); strcat(pStrReturn, STARTCODE ) ; //Startcode, always like this; intCode = (systemCode << 10) | rawChannelCode; for ( i = 13; i >= 0; --i) { if ((intCode>>i) & 1) { strcat(pStrReturn, TT ); if (i % 2 == 0) { checksum2++; } else { checksum1++; } } else { strcat(pStrReturn,A); } } if (checksum1 %2 == 0) { strcat(pStrReturn, TT ); } else { strcat(pStrReturn, A) ; //1st checksum } if (checksum2 %2 == 0) { strcat(pStrReturn, TT ); } else { strcat(pStrReturn, A ) ; //2nd checksum } if (DimStyle == 1) { intFade = 11 << 4; //Smooth } else { intFade = 1 << 4; //Instant } switch ( Level ) { case 0 : intCode = (10 | intFade) ; //Concat level and fade break; case 1 : intCode = (1 | intFade) ; //Concat level and fade break; case 2 : intCode = (2 | intFade) ; //Concat level and fade break; case 3 : intCode = (3 | intFade) ; //Concat level and fade break; case 4 : intCode = (4 | intFade) ; //Concat level and fade break; case 5 : intCode = (5 | intFade) ; //Concat level and fade break; case 6 : intCode = (6 | intFade) ; //Concat level and fade break; case 7 : intCode = (7 | intFade) ; //Concat level and fade break; case 8 : intCode = (8 | intFade) ; //Concat level and fade break; case 9 : intCode = (9 | intFade) ; //Concat level and fade break; case 10 : default : intCode = (0 | intFade) ; //Concat level and fade break; } checksum1 = 0; checksum2 = 0; for (i = 0; i < 6; ++i) { if ((intCode>>i) & 1) { strcat(pStrReturn, TT); if (i % 2 == 0) { checksum1++; } else { checksum2++; } } else { strcat(pStrReturn, A ); } } if (checksum1 %2 == 0) { strcat(pStrReturn, TT); } else { strcat(pStrReturn, A ) ; //2nd checksum } if (checksum2 %2 == 0) { strcat(pStrReturn, TT ); } else { strcat(pStrReturn, A ) ; //2nd checksum } strcat(pStrReturn, "+"); return strlen(pStrReturn); } int createRisingSunString(const char * pCodeStr, const char * pUnitStr, const char * pOn_offStr, char * pTxStr) { * pTxStr = '\0'; /* Make sure tx string is empty */ int on_offCode; int unit; int code; int i; on_offCode = atoi(pOn_offStr); /* ON/OFF 0..1 */ code = atoi (pCodeStr); unit = atoi (pUnitStr); #ifdef RFCMD_DEBUG printf("Code: %s, unit: %s, on_off: %d\n", pCodeStr, pUnitStr, on_offCode); #endif /* check converted parameters for validity */ if((code < 1) || (code > 4) || (unit < 1) || (unit > 4) || (on_offCode < 0) || (on_offCode > 1)) { } else { strcat(pTxStr,"S.e"); for (i = 1; i <= 4; ++i) { if (i == code) { strcat (pTxStr, ".e.e"); } else { strcat (pTxStr, "e..e"); } } for (i = 1; i <= 4; ++i) { if (i == unit) { strcat (pTxStr, ".e.e"); } else { strcat (pTxStr, "e..e"); } } if (on_offCode >= 1) { strcat(pTxStr,"e..ee..ee..ee..e+"); //the "turn on"-code } else { strcat(pTxStr,"e..ee..ee..e.e.e+"); //the "turn off"-code } } return strlen(pTxStr); } unsigned int everflourish_find_code(unsigned int x) { unsigned int bits[16] = { 0xf ,0xa ,0x7 ,0xe, 0xf ,0xd ,0x9 ,0x1, 0x1 ,0x2 ,0x4 ,0x8, 0x3 ,0x6 ,0xc ,0xb }; unsigned int bit = 1; unsigned int res = 0x5; int i; unsigned int lo,hi; if ((x&0x3)==3) { lo = x & 0x00ff; hi = x & 0xff00; lo += 4; if (lo>0x100) lo = 0x12; x = lo | hi; } for(i=0;i<16;i++) { if (x&bit) { res = res ^ bits[i]; } bit = bit << 1; } return res; } int createEverFlourishString(const char * pUnitStr, const char * pLevelStr, char * pTxStr) { int len = 0; int level; int unit; unsigned int check; int i; unit = atoi(pUnitStr); level = atoi(pLevelStr); /* ON=15, OFF=0, LEARN=10 */ check = everflourish_find_code(unit); #ifdef RFCMD_DEBUG printf("unit: %d, level: %d\n", unit, level); #endif /* check converted parameters for validity */ if((unit < 0) || (unit > 0xffff) || (level < 0) || (level > 15)) { } else { const char ssss = 85; const char sssl = 84; // 0 const char slss = 69; // 1 const char bits[2] = {sssl,slss}; int i; char preamble[] = {'R', 5, 'T', 114,60,1,1,105,ssss,ssss}; memcpy(pTxStr, preamble, sizeof(preamble)); len += sizeof(preamble); for(i=15;i>=0;i--) pTxStr[len++]=bits[(unit>>i)&0x01]; for(i=3;i>=0;i--) pTxStr[len++]=bits[(check>>i)&0x01]; for(i=3;i>=0;i--) pTxStr[len++]=bits[(level>>i)&0x01]; pTxStr[len++] = ssss; pTxStr[len++] = '+'; } pTxStr[len] = '\0'; return strlen(pTxStr); } void printUsage(void) { printf("Usage: rfcmd DEVICE PROTOCOL [PROTOCOL_ARGUMENTS] \n"); printf("\n"); #ifdef LIBFTDI printf("\t DEVICE: /dev/ttyUSB[0..n] | LIBUSB\n" ); #else printf("\t DEVICE: /dev/ttyUSB[0..n]\n" ); #endif printf("\t PROTOCOLS: NEXA, SARTANO, WAVEMAN, IKEA, RISINGSUN, EVERFLOURISH\n" ); printf("\n"); printf("\t PROTOCOL ARGUMENTS - NEXA, WAVEMAN:\n"); printf("\t\tHOUSE_CODE: A..P\n\t\tCHANNEL: 1..16\n\t\tOFF_ON: 0..1\n" ); printf("\n"); printf("\t PROTOCOL ARGUMENTS - SARTANO:\n"); printf("\t\tCHANNEL: 0000000000..1111111111\n\t\tOFF_ON: 0..1\n" ); printf("\n"); printf("\t PROTOCOL ARGUMENTS - IKEA:\n"); printf("\t\tSYSTEM: 1..16\n\t\tDEVICE: 1..10\n"); printf("\t\tDIM_LEVEL: 0..10\n\t\tDIM_STYLE: 0..1\n" ); printf("\n"); printf("\t PROTOCOL ARGUMENTS - RISINGSUN:\n"); printf("\t\tCODE: 1..4\n\t\tDEVICE: 1..4\n"); printf("\t\tOFF_ON: 0..1\n" ); printf("\n"); printf("\t PROTOCOL ARGUMENTS - EVERFLOURISH:\n"); printf("\t\tDEVICE: 0..65535\n"); printf("\t\tLEVEL: 0=off, 10=learn, 15=on\n" ); printf("\n"); printf("Report bugs to \n"); } void printVersion(void) { printf("%s v%s\n", PROG_NAME, PROG_VERSION); printf("\n"); printf("Copyright (C) Tord Andersson 2007\n"); printf("\n"); printf("Written by:\n"); printf("Tord Andersson, Micke Prag, Gudmund Berggren, Tapani Rintala\n"); printf("and Johan Ström\n"); }