diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 93e8d8c..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9179a8a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.DS_Store +*.o +status.json +configuration.inc.php diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 2aeeef9..a783c56 --- a/README.md +++ b/README.md @@ -4,6 +4,11 @@ Have you ever wanted to wirelessly control power outlets from your phone using a **Blog Post:** [TimLeland.com/wireless-power-outlets](https://timleland.com/wireless-power-outlets/) +Homebridge +* A sample configuration file for the [Homebridge HTTP plugin](https://github.com/rudders/homebridge-http) is included at `homebridge-http.config.json.sample` +* You may want to set a longer polling interval following the instructions [here](https://github.com/rudders/homebridge-http/issues/76) +* Please note that the status tracking for Homebridge does not yet support receiving RF signals from the remote + Voice Control Outlets (Follow up Guides) * [Siri using HomeBridge](https://timleland.com/use-siri-to-control-wireless-power-outlets-homebridge/) * [Google Home](https://timleland.com/use-google-home-to-control-wireless-power-outlets/) diff --git a/RFSniffer b/RFSniffer deleted file mode 100755 index 285328c..0000000 Binary files a/RFSniffer and /dev/null differ diff --git a/RFSource/.DS_Store b/RFSource/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/RFSource/.DS_Store and /dev/null differ diff --git a/RFSource/Makefile b/RFSource/Makefile old mode 100644 new mode 100755 index b5f09f2..a9d3d79 --- a/RFSource/Makefile +++ b/RFSource/Makefile @@ -1,14 +1,12 @@ -all: send codesend RFSniffer - -send: RCSwitch.o send.o - $(CXX) $(CXXFLAGS) $(LDFLAGS) $+ -o $@ -lwiringPi - -codesend: RCSwitch.o codesend.o - $(CXX) $(CXXFLAGS) $(LDFLAGS) $+ -o $@ -lwiringPi - -RFSniffer: RCSwitch.o RFSniffer.o +CXXFLAGS = -D RPI +all: codesend sniffer + +codesend: RCSwitch.o shared_mutex.c RFOutlet.c codesend.o + $(CXX) $(CXXFLAGS) $(LDFLAGS) $+ -o $@ -lwiringPi -pthread -lrt + +sniffer: RCSwitch.o RFOutlet.c sniffer.o $(CXX) $(CXXFLAGS) $(LDFLAGS) $+ -o $@ -lwiringPi - - + clean: - $(RM) *.o send codesend servo RFSniffer \ No newline at end of file + $(RM) *.o codesend sniffer + diff --git a/RFSource/RCSwitch.cpp b/RFSource/RCSwitch.cpp old mode 100644 new mode 100755 index 67e6e30..6389dbb --- a/RFSource/RCSwitch.cpp +++ b/RFSource/RCSwitch.cpp @@ -5,9 +5,16 @@ Contributors: - Andre Koehler / info(at)tomate-online(dot)de - Gordeev Andrey Vladimirovich / gordeev(at)openpyro(dot)com - - Skineffect / http://forum.ardumote.com/viewtopic.php?f=2&t=48 + - Skineffect / http://forum.ardumote.com/viewtopic.php?f=2&t=46 + - Dominik Fischer / dom_fischer(at)web(dot)de + - Frank Oltmanns / .(at)gmail(dot)com + - Andreas Steinel / A.(at)gmail(dot)com + - Max Horn / max(at)quendi(dot)de + - Robert ter Vehn / .(at)gmail(dot)com + - Johann Richard / .(at)gmail(dot)com + - Vlad Gheorghe / .(at)gmail(dot)com https://github.com/vgheo - Project home: http://code.google.com/p/rc-switch/ + Project home: https://github.com/sui77/rc-switch/ This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -26,47 +33,109 @@ #include "RCSwitch.h" -unsigned long RCSwitch::nReceivedValue = NULL; +#ifdef RaspberryPi + // PROGMEM and _P functions are for AVR based microprocessors, + // so we must normalize these for the ARM processor: + #define PROGMEM + #define memcpy_P(dest, src, num) memcpy((dest), (src), (num)) +#endif + +#ifdef ESP8266 + // interrupt handler and related code must be in RAM on ESP8266, + // according to issue #46. + #define RECEIVE_ATTR ICACHE_RAM_ATTR +#else + #define RECEIVE_ATTR +#endif + + +/* Format for protocol definitions: + * {pulselength, Sync bit, "0" bit, "1" bit} + * + * pulselength: pulse length in microseconds, e.g. 350 + * Sync bit: {1, 31} means 1 high pulse and 31 low pulses + * (perceived as a 31*pulselength long pulse, total length of sync bit is + * 32*pulselength microseconds), i.e: + * _ + * | |_______________________________ (don't count the vertical bars) + * "0" bit: waveform for a data bit of value "0", {1, 3} means 1 high pulse + * and 3 low pulses, total length (1+3)*pulselength, i.e: + * _ + * | |___ + * "1" bit: waveform for a data bit of value "1", e.g. {3,1}: + * ___ + * | |_ + * + * These are combined to form Tri-State bits when sending or receiving codes. + */ +#ifdef ESP8266 +static const RCSwitch::Protocol proto[] = { +#else +static const RCSwitch::Protocol PROGMEM proto[] = { +#endif + { 350, { 1, 31 }, { 1, 3 }, { 3, 1 }, false }, // protocol 1 + { 650, { 1, 10 }, { 1, 2 }, { 2, 1 }, false }, // protocol 2 + { 100, { 30, 71 }, { 4, 11 }, { 9, 6 }, false }, // protocol 3 + { 380, { 1, 6 }, { 1, 3 }, { 3, 1 }, false }, // protocol 4 + { 500, { 6, 14 }, { 1, 2 }, { 2, 1 }, false }, // protocol 5 + { 450, { 23, 1 }, { 1, 2 }, { 2, 1 }, true } // protocol 6 (HT6P20B) +}; + +enum { + numProto = sizeof(proto) / sizeof(proto[0]) +}; + +#if not defined( RCSwitchDisableReceiving ) +unsigned long RCSwitch::nReceivedValue = 0; unsigned int RCSwitch::nReceivedBitlength = 0; unsigned int RCSwitch::nReceivedDelay = 0; unsigned int RCSwitch::nReceivedProtocol = 0; -unsigned int RCSwitch::timings[RCSWITCH_MAX_CHANGES]; int RCSwitch::nReceiveTolerance = 60; +const unsigned int RCSwitch::nSeparationLimit = 4300; +// separationLimit: minimum microseconds between received codes, closer codes are ignored. +// according to discussion on issue #14 it might be more suitable to set the separation +// limit to the same time as the 'low' part of the sync signal for the current protocol. +unsigned int RCSwitch::timings[RCSWITCH_MAX_CHANGES]; +#endif RCSwitch::RCSwitch() { - this->nReceiverInterrupt = -1; this->nTransmitterPin = -1; - RCSwitch::nReceivedValue = NULL; - this->setPulseLength(350); this->setRepeatTransmit(10); - this->setReceiveTolerance(60); this->setProtocol(1); + #if not defined( RCSwitchDisableReceiving ) + this->nReceiverInterrupt = -1; + this->setReceiveTolerance(60); + RCSwitch::nReceivedValue = 0; + #endif } /** * Sets the protocol to send. */ +void RCSwitch::setProtocol(Protocol protocol) { + this->protocol = protocol; +} + +/** + * Sets the protocol to send, from a list of predefined protocols + */ void RCSwitch::setProtocol(int nProtocol) { - this->nProtocol = nProtocol; - if (nProtocol == 1){ - this->setPulseLength(350); - } - else if (nProtocol == 2) { - this->setPulseLength(650); + if (nProtocol < 1 || nProtocol > numProto) { + nProtocol = 1; // TODO: trigger an error, e.g. "bad protocol" ??? } +#ifdef ESP8266 + this->protocol = proto[nProtocol-1]; +#else + memcpy_P(&this->protocol, &proto[nProtocol-1], sizeof(Protocol)); +#endif } /** * Sets the protocol to send with pulse length in microseconds. */ void RCSwitch::setProtocol(int nProtocol, int nPulseLength) { - this->nProtocol = nProtocol; - if (nProtocol == 1){ - this->setPulseLength(nPulseLength); - } - else if (nProtocol == 2) { - this->setPulseLength(nPulseLength); - } + setProtocol(nProtocol); + this->setPulseLength(nPulseLength); } @@ -74,7 +143,7 @@ void RCSwitch::setProtocol(int nProtocol, int nPulseLength) { * Sets pulse length in microseconds */ void RCSwitch::setPulseLength(int nPulseLength) { - this->nPulseLength = nPulseLength; + this->protocol.pulseLength = nPulseLength; } /** @@ -87,9 +156,11 @@ void RCSwitch::setRepeatTransmit(int nRepeatTransmit) { /** * Set Receiving Tolerance */ +#if not defined( RCSwitchDisableReceiving ) void RCSwitch::setReceiveTolerance(int nPercent) { RCSwitch::nReceiveTolerance = nPercent; } +#endif /** @@ -109,6 +180,26 @@ void RCSwitch::disableTransmit() { this->nTransmitterPin = -1; } +/** + * Switch a remote switch on (Type D REV) + * + * @param sGroup Code of the switch group (A,B,C,D) + * @param nDevice Number of the switch itself (1..3) + */ +void RCSwitch::switchOn(char sGroup, int nDevice) { + this->sendTriState( this->getCodeWordD(sGroup, nDevice, true) ); +} + +/** + * Switch a remote switch off (Type D REV) + * + * @param sGroup Code of the switch group (A,B,C,D) + * @param nDevice Number of the switch itself (1..3) + */ +void RCSwitch::switchOff(char sGroup, int nDevice) { + this->sendTriState( this->getCodeWordD(sGroup, nDevice, false) ); +} + /** * Switch a remote switch on (Type C Intertechno) * @@ -152,287 +243,291 @@ void RCSwitch::switchOff(int nAddressCode, int nChannelCode) { } /** + * Deprecated, use switchOn(const char* sGroup, const char* sDevice) instead! * Switch a remote switch on (Type A with 10 pole DIP switches) * * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") - * @param nChannelCode Number of the switch itself (1..4) + * @param nChannelCode Number of the switch itself (1..5) */ -void RCSwitch::switchOn(char* sGroup, int nChannel) { - this->sendTriState( this->getCodeWordA(sGroup, nChannel, true) ); +void RCSwitch::switchOn(const char* sGroup, int nChannel) { + const char* code[6] = { "00000", "10000", "01000", "00100", "00010", "00001" }; + this->switchOn(sGroup, code[nChannel]); } /** + * Deprecated, use switchOff(const char* sGroup, const char* sDevice) instead! * Switch a remote switch off (Type A with 10 pole DIP switches) * * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") - * @param nChannelCode Number of the switch itself (1..4) + * @param nChannelCode Number of the switch itself (1..5) */ -void RCSwitch::switchOff(char* sGroup, int nChannel) { - this->sendTriState( this->getCodeWordA(sGroup, nChannel, false) ); +void RCSwitch::switchOff(const char* sGroup, int nChannel) { + const char* code[6] = { "00000", "10000", "01000", "00100", "00010", "00001" }; + this->switchOff(sGroup, code[nChannel]); } /** - * Returns a char[13], representing the Code Word to be send. - * A Code Word consists of 9 address bits, 3 data bits and one sync bit but in our case only the first 8 address bits and the last 2 data bits were used. - * A Code Bit can have 4 different states: "F" (floating), "0" (low), "1" (high), "S" (synchronous bit) - * - * +-------------------------------+--------------------------------+-----------------------------------------+-----------------------------------------+----------------------+------------+ - * | 4 bits address (switch group) | 4 bits address (switch number) | 1 bit address (not used, so never mind) | 1 bit address (not used, so never mind) | 2 data bits (on|off) | 1 sync bit | - * | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | F | F | on=FF off=F0 | S | - * +-------------------------------+--------------------------------+-----------------------------------------+-----------------------------------------+----------------------+------------+ + * Switch a remote switch on (Type A with 10 pole DIP switches) * - * @param nAddressCode Number of the switch group (1..4) - * @param nChannelCode Number of the switch itself (1..4) - * @param bStatus Wether to switch on (true) or off (false) + * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") + * @param sDevice Code of the switch device (refers to DIP switches 6..10 (A..E) where "1" = on and "0" = off, if all DIP switches are on it's "11111") + */ +void RCSwitch::switchOn(const char* sGroup, const char* sDevice) { + this->sendTriState( this->getCodeWordA(sGroup, sDevice, true) ); +} + +/** + * Switch a remote switch off (Type A with 10 pole DIP switches) * - * @return char[13] + * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") + * @param sDevice Code of the switch device (refers to DIP switches 6..10 (A..E) where "1" = on and "0" = off, if all DIP switches are on it's "11111") */ -char* RCSwitch::getCodeWordB(int nAddressCode, int nChannelCode, boolean bStatus) { - int nReturnPos = 0; - static char sReturn[13]; - - const char* code[5] = { "FFFF", "0FFF", "F0FF", "FF0F", "FFF0" }; - if (nAddressCode < 1 || nAddressCode > 4 || nChannelCode < 1 || nChannelCode > 4) { - return '\0'; - } - for (int i = 0; i<4; i++) { - sReturn[nReturnPos++] = code[nAddressCode][i]; - } - - for (int i = 0; i<4; i++) { - sReturn[nReturnPos++] = code[nChannelCode][i]; - } - - sReturn[nReturnPos++] = 'F'; - sReturn[nReturnPos++] = 'F'; - sReturn[nReturnPos++] = 'F'; - - if (bStatus) { - sReturn[nReturnPos++] = 'F'; - } else { - sReturn[nReturnPos++] = '0'; - } - - sReturn[nReturnPos] = '\0'; - - return sReturn; +void RCSwitch::switchOff(const char* sGroup, const char* sDevice) { + this->sendTriState( this->getCodeWordA(sGroup, sDevice, false) ); } /** - * Like getCodeWord (Type A) + * Returns a char[13], representing the code word to be send. + * */ -char* RCSwitch::getCodeWordA(char* sGroup, int nChannelCode, boolean bStatus) { - int nReturnPos = 0; - static char sReturn[13]; +char* RCSwitch::getCodeWordA(const char* sGroup, const char* sDevice, bool bStatus) { + static char sReturn[13]; + int nReturnPos = 0; - const char* code[6] = { "FFFFF", "0FFFF", "F0FFF", "FF0FF", "FFF0F", "FFFF0" }; + for (int i = 0; i < 5; i++) { + sReturn[nReturnPos++] = (sGroup[i] == '0') ? 'F' : '0'; + } - if (nChannelCode < 1 || nChannelCode > 5) { - return '\0'; + for (int i = 0; i < 5; i++) { + sReturn[nReturnPos++] = (sDevice[i] == '0') ? 'F' : '0'; } - - for (int i = 0; i<5; i++) { - if (sGroup[i] == '0') { - sReturn[nReturnPos++] = 'F'; - } else if (sGroup[i] == '1') { - sReturn[nReturnPos++] = '0'; - } else { - return '\0'; - } + + sReturn[nReturnPos++] = bStatus ? '0' : 'F'; + sReturn[nReturnPos++] = bStatus ? 'F' : '0'; + + sReturn[nReturnPos] = '\0'; + return sReturn; +} + +/** + * Encoding for type B switches with two rotary/sliding switches. + * + * The code word is a tristate word and with following bit pattern: + * + * +-----------------------------+-----------------------------+----------+------------+ + * | 4 bits address | 4 bits address | 3 bits | 1 bit | + * | switch group | switch number | not used | on / off | + * | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | FFF | on=F off=0 | + * +-----------------------------+-----------------------------+----------+------------+ + * + * @param nAddressCode Number of the switch group (1..4) + * @param nChannelCode Number of the switch itself (1..4) + * @param bStatus Whether to switch on (true) or off (false) + * + * @return char[13], representing a tristate code word of length 12 + */ +char* RCSwitch::getCodeWordB(int nAddressCode, int nChannelCode, bool bStatus) { + static char sReturn[13]; + int nReturnPos = 0; + + if (nAddressCode < 1 || nAddressCode > 4 || nChannelCode < 1 || nChannelCode > 4) { + return 0; } - - for (int i = 0; i<5; i++) { - sReturn[nReturnPos++] = code[ nChannelCode ][i]; + + for (int i = 1; i <= 4; i++) { + sReturn[nReturnPos++] = (nAddressCode == i) ? '0' : 'F'; } - - if (bStatus) { - sReturn[nReturnPos++] = '0'; - sReturn[nReturnPos++] = 'F'; - } else { - sReturn[nReturnPos++] = 'F'; - sReturn[nReturnPos++] = '0'; + + for (int i = 1; i <= 4; i++) { + sReturn[nReturnPos++] = (nChannelCode == i) ? '0' : 'F'; } - sReturn[nReturnPos] = '\0'; + sReturn[nReturnPos++] = 'F'; + sReturn[nReturnPos++] = 'F'; + sReturn[nReturnPos++] = 'F'; + + sReturn[nReturnPos++] = bStatus ? 'F' : '0'; + + sReturn[nReturnPos] = '\0'; return sReturn; } /** * Like getCodeWord (Type C = Intertechno) */ -char* RCSwitch::getCodeWordC(char sFamily, int nGroup, int nDevice, boolean bStatus) { +char* RCSwitch::getCodeWordC(char sFamily, int nGroup, int nDevice, bool bStatus) { static char sReturn[13]; int nReturnPos = 0; - - if ( (byte)sFamily < 97 || (byte)sFamily > 112 || nGroup < 1 || nGroup > 4 || nDevice < 1 || nDevice > 4) { - return '\0'; + + int nFamily = (int)sFamily - 'a'; + if ( nFamily < 0 || nFamily > 15 || nGroup < 1 || nGroup > 4 || nDevice < 1 || nDevice > 4) { + return 0; } - char* sDeviceGroupCode = dec2binWzerofill( (nDevice-1) + (nGroup-1)*4, 4 ); - char familycode[16][5] = { "0000", "F000", "0F00", "FF00", "00F0", "F0F0", "0FF0", "FFF0", "000F", "F00F", "0F0F", "FF0F", "00FF", "F0FF", "0FFF", "FFFF" }; - for (int i = 0; i<4; i++) { - sReturn[nReturnPos++] = familycode[ (int)sFamily - 97 ][i]; - } - for (int i = 0; i<4; i++) { - sReturn[nReturnPos++] = (sDeviceGroupCode[3-i] == '1' ? 'F' : '0'); - } + // encode the family into four bits + sReturn[nReturnPos++] = (nFamily & 1) ? 'F' : '0'; + sReturn[nReturnPos++] = (nFamily & 2) ? 'F' : '0'; + sReturn[nReturnPos++] = (nFamily & 4) ? 'F' : '0'; + sReturn[nReturnPos++] = (nFamily & 8) ? 'F' : '0'; + + // encode the device and group + sReturn[nReturnPos++] = ((nDevice-1) & 1) ? 'F' : '0'; + sReturn[nReturnPos++] = ((nDevice-1) & 2) ? 'F' : '0'; + sReturn[nReturnPos++] = ((nGroup-1) & 1) ? 'F' : '0'; + sReturn[nReturnPos++] = ((nGroup-1) & 2) ? 'F' : '0'; + + // encode the status code sReturn[nReturnPos++] = '0'; sReturn[nReturnPos++] = 'F'; sReturn[nReturnPos++] = 'F'; - if (bStatus) { - sReturn[nReturnPos++] = 'F'; - } else { - sReturn[nReturnPos++] = '0'; - } + sReturn[nReturnPos++] = bStatus ? 'F' : '0'; + sReturn[nReturnPos] = '\0'; return sReturn; } /** - * Sends a Code Word - * @param sCodeWord /^[10FS]*$/ -> see getCodeWord + * Encoding for the REV Switch Type + * + * The code word is a tristate word and with following bit pattern: + * + * +-----------------------------+-------------------+----------+--------------+ + * | 4 bits address | 3 bits address | 3 bits | 2 bits | + * | switch group | device number | not used | on / off | + * | A=1FFF B=F1FF C=FF1F D=FFF1 | 1=0FF 2=F0F 3=FF0 | 000 | on=10 off=01 | + * +-----------------------------+-------------------+----------+--------------+ + * + * Source: http://www.the-intruder.net/funksteckdosen-von-rev-uber-arduino-ansteuern/ + * + * @param sGroup Name of the switch group (A..D, resp. a..d) + * @param nDevice Number of the switch itself (1..3) + * @param bStatus Whether to switch on (true) or off (false) + * + * @return char[13], representing a tristate code word of length 12 */ -void RCSwitch::sendTriState(char* sCodeWord) { - for (int nRepeat=0; nRepeatsendT0(); - break; - case 'F': - this->sendTF(); - break; - case '1': - this->sendT1(); - break; - } - i++; - } - this->sendSync(); +char* RCSwitch::getCodeWordD(char sGroup, int nDevice, bool bStatus) { + static char sReturn[13]; + int nReturnPos = 0; + + // sGroup must be one of the letters in "abcdABCD" + int nGroup = (sGroup >= 'a') ? (int)sGroup - 'a' : (int)sGroup - 'A'; + if ( nGroup < 0 || nGroup > 3 || nDevice < 1 || nDevice > 3) { + return 0; } -} -void RCSwitch::send(unsigned long Code, unsigned int length) { - this->send( this->dec2binWzerofill(Code, length) ); -} + for (int i = 0; i < 4; i++) { + sReturn[nReturnPos++] = (nGroup == i) ? '1' : 'F'; + } -void RCSwitch::send(char* sCodeWord) { - for (int nRepeat=0; nRepeatsend0(); - break; - case '1': - this->send1(); - break; - } - i++; - } - this->sendSync(); + for (int i = 1; i <= 3; i++) { + sReturn[nReturnPos++] = (nDevice == i) ? '1' : 'F'; } -} -void RCSwitch::transmit(int nHighPulses, int nLowPulses) { - boolean disabled_Receive = false; - int nReceiverInterrupt_backup = nReceiverInterrupt; - if (this->nTransmitterPin != -1) { - if (this->nReceiverInterrupt != -1) { - this->disableReceive(); - disabled_Receive = true; - } - digitalWrite(this->nTransmitterPin, HIGH); - delayMicroseconds( this->nPulseLength * nHighPulses); - digitalWrite(this->nTransmitterPin, LOW); - delayMicroseconds( this->nPulseLength * nLowPulses); - if(disabled_Receive){ - this->enableReceive(nReceiverInterrupt_backup); - } - } -} -/** - * Sends a "0" Bit - * _ - * Waveform Protocol 1: | |___ - * _ - * Waveform Protocol 2: | |__ - */ -void RCSwitch::send0() { - if (this->nProtocol == 1){ - this->transmit(1,3); - } - else if (this->nProtocol == 2) { - this->transmit(1,2); - } -} + sReturn[nReturnPos++] = '0'; + sReturn[nReturnPos++] = '0'; + sReturn[nReturnPos++] = '0'; -/** - * Sends a "1" Bit - * ___ - * Waveform Protocol 1: | |_ - * __ - * Waveform Protocol 2: | |_ - */ -void RCSwitch::send1() { - if (this->nProtocol == 1){ - this->transmit(3,1); - } - else if (this->nProtocol == 2) { - this->transmit(2,1); - } -} + sReturn[nReturnPos++] = bStatus ? '1' : '0'; + sReturn[nReturnPos++] = bStatus ? '0' : '1'; + sReturn[nReturnPos] = '\0'; + return sReturn; +} /** - * Sends a Tri-State "0" Bit - * _ _ - * Waveform: | |___| |___ + * @param sCodeWord a tristate code word consisting of the letter 0, 1, F */ -void RCSwitch::sendT0() { - this->transmit(1,3); - this->transmit(1,3); +void RCSwitch::sendTriState(const char* sCodeWord) { + // turn the tristate code word into the corresponding bit pattern, then send it + unsigned long code = 0; + unsigned int length = 0; + for (const char* p = sCodeWord; *p; p++) { + code <<= 2L; + switch (*p) { + case '0': + // bit pattern 00 + break; + case 'F': + // bit pattern 01 + code |= 1L; + break; + case '1': + // bit pattern 11 + code |= 3L; + break; + } + length += 2; + } + this->send(code, length); } /** - * Sends a Tri-State "1" Bit - * ___ ___ - * Waveform: | |_| |_ + * @param sCodeWord a binary code word consisting of the letter 0, 1 */ -void RCSwitch::sendT1() { - this->transmit(3,1); - this->transmit(3,1); +void RCSwitch::send(const char* sCodeWord) { + // turn the tristate code word into the corresponding bit pattern, then send it + unsigned long code = 0; + unsigned int length = 0; + for (const char* p = sCodeWord; *p; p++) { + code <<= 1L; + if (*p != '0') + code |= 1L; + length++; + } + this->send(code, length); } /** - * Sends a Tri-State "F" Bit - * _ ___ - * Waveform: | |___| |_ + * Transmit the first 'length' bits of the integer 'code'. The + * bits are sent from MSB to LSB, i.e., first the bit at position length-1, + * then the bit at position length-2, and so on, till finally the bit at position 0. */ -void RCSwitch::sendTF() { - this->transmit(1,3); - this->transmit(3,1); +void RCSwitch::send(unsigned long code, unsigned int length) { + if (this->nTransmitterPin == -1) + return; + +#if not defined( RCSwitchDisableReceiving ) + // make sure the receiver is disabled while we transmit + int nReceiverInterrupt_backup = nReceiverInterrupt; + if (nReceiverInterrupt_backup != -1) { + this->disableReceive(); + } +#endif + + for (int nRepeat = 0; nRepeat < nRepeatTransmit; nRepeat++) { + for (int i = length-1; i >= 0; i--) { + if (code & (1L << i)) + this->transmit(protocol.one); + else + this->transmit(protocol.zero); + } + this->transmit(protocol.syncFactor); + } + +#if not defined( RCSwitchDisableReceiving ) + // enable receiver again if we just disabled it + if (nReceiverInterrupt_backup != -1) { + this->enableReceive(nReceiverInterrupt_backup); + } +#endif } /** - * Sends a "Sync" Bit - * _ - * Waveform Protocol 1: | |_______________________________ - * _ - * Waveform Protocol 2: | |__________ + * Transmit a single high-low pulse. */ -void RCSwitch::sendSync() { - - if (this->nProtocol == 1){ - this->transmit(1,31); - } - else if (this->nProtocol == 2) { - this->transmit(1,10); - } +void RCSwitch::transmit(HighLow pulses) { + uint8_t firstLogicLevel = (this->protocol.invertedSignal) ? LOW : HIGH; + uint8_t secondLogicLevel = (this->protocol.invertedSignal) ? HIGH : LOW; + + digitalWrite(this->nTransmitterPin, firstLogicLevel); + delayMicroseconds( this->protocol.pulseLength * pulses.high); + digitalWrite(this->nTransmitterPin, secondLogicLevel); + delayMicroseconds( this->protocol.pulseLength * pulses.low); } + +#if not defined( RCSwitchDisableReceiving ) /** * Enable receiving data */ @@ -443,9 +538,13 @@ void RCSwitch::enableReceive(int interrupt) { void RCSwitch::enableReceive() { if (this->nReceiverInterrupt != -1) { - RCSwitch::nReceivedValue = NULL; - RCSwitch::nReceivedBitlength = NULL; + RCSwitch::nReceivedValue = 0; + RCSwitch::nReceivedBitlength = 0; +#if defined(RaspberryPi) // Raspberry Pi wiringPiISR(this->nReceiverInterrupt, INT_EDGE_BOTH, &handleInterrupt); +#else // Arduino + attachInterrupt(this->nReceiverInterrupt, handleInterrupt, CHANGE); +#endif } } @@ -453,19 +552,22 @@ void RCSwitch::enableReceive() { * Disable receiving data */ void RCSwitch::disableReceive() { +#if not defined(RaspberryPi) // Arduino + detachInterrupt(this->nReceiverInterrupt); +#endif // For Raspberry Pi (wiringPi) you can't unregister the ISR this->nReceiverInterrupt = -1; } bool RCSwitch::available() { - return RCSwitch::nReceivedValue != NULL; + return RCSwitch::nReceivedValue != 0; } void RCSwitch::resetAvailable() { - RCSwitch::nReceivedValue = NULL; + RCSwitch::nReceivedValue = 0; } unsigned long RCSwitch::getReceivedValue() { - return RCSwitch::nReceivedValue; + return RCSwitch::nReceivedValue; } unsigned int RCSwitch::getReceivedBitlength() { @@ -481,139 +583,115 @@ unsigned int RCSwitch::getReceivedProtocol() { } unsigned int* RCSwitch::getReceivedRawdata() { - return RCSwitch::timings; + return RCSwitch::timings; +} + +/* helper function for the receiveProtocol method */ +static inline unsigned int diff(int A, int B) { + return abs(A - B); } /** * */ -bool RCSwitch::receiveProtocol1(unsigned int changeCount){ +bool RECEIVE_ATTR RCSwitch::receiveProtocol(const int p, unsigned int changeCount) { +#ifdef ESP8266 + const Protocol &pro = proto[p-1]; +#else + Protocol pro; + memcpy_P(&pro, &proto[p-1], sizeof(Protocol)); +#endif + + unsigned long code = 0; + //Assuming the longer pulse length is the pulse captured in timings[0] + const unsigned int syncLengthInPulses = ((pro.syncFactor.low) > (pro.syncFactor.high)) ? (pro.syncFactor.low) : (pro.syncFactor.high); + const unsigned int delay = RCSwitch::timings[0] / syncLengthInPulses; + const unsigned int delayTolerance = delay * RCSwitch::nReceiveTolerance / 100; - unsigned long code = 0; - unsigned long delay = RCSwitch::timings[0] / 31; - unsigned long delayTolerance = delay * RCSwitch::nReceiveTolerance * 0.01; - - for (int i = 1; i delay-delayTolerance && RCSwitch::timings[i] < delay+delayTolerance && RCSwitch::timings[i+1] > delay*3-delayTolerance && RCSwitch::timings[i+1] < delay*3+delayTolerance) { - code = code << 1; - } else if (RCSwitch::timings[i] > delay*3-delayTolerance && RCSwitch::timings[i] < delay*3+delayTolerance && RCSwitch::timings[i+1] > delay-delayTolerance && RCSwitch::timings[i+1] < delay+delayTolerance) { - code+=1; - code = code << 1; - } else { + /* For protocols that start low, the sync period looks like + * _________ + * _____________| |XXXXXXXXXXXX| + * + * |--1st dur--|-2nd dur-|-Start data-| + * + * The 3rd saved duration starts the data. + * + * For protocols that start high, the sync period looks like + * + * ______________ + * | |____________|XXXXXXXXXXXXX| + * + * |-filtered out-|--1st dur--|--Start data--| + * + * The 2nd saved duration starts the data + */ + const unsigned int firstDataTiming = (pro.invertedSignal) ? (2) : (1); + + for (unsigned int i = firstDataTiming; i < changeCount - 1; i += 2) { + code <<= 1; + if (diff(RCSwitch::timings[i], delay * pro.zero.high) < delayTolerance && + diff(RCSwitch::timings[i + 1], delay * pro.zero.low) < delayTolerance) { + // zero + } else if (diff(RCSwitch::timings[i], delay * pro.one.high) < delayTolerance && + diff(RCSwitch::timings[i + 1], delay * pro.one.low) < delayTolerance) { + // one + code |= 1; + } else { // Failed - i = changeCount; - code = 0; - } - } - code = code >> 1; - if (changeCount > 6) { // ignore < 4bit values as there are no devices sending 4bit values => noise - RCSwitch::nReceivedValue = code; - RCSwitch::nReceivedBitlength = changeCount / 2; - RCSwitch::nReceivedDelay = delay; - RCSwitch::nReceivedProtocol = 1; + return false; + } } - if (code == 0){ - return false; - }else if (code != 0){ - return true; - } - - -} - -bool RCSwitch::receiveProtocol2(unsigned int changeCount){ - - unsigned long code = 0; - unsigned long delay = RCSwitch::timings[0] / 10; - unsigned long delayTolerance = delay * RCSwitch::nReceiveTolerance * 0.01; - - for (int i = 1; i delay-delayTolerance && RCSwitch::timings[i] < delay+delayTolerance && RCSwitch::timings[i+1] > delay*2-delayTolerance && RCSwitch::timings[i+1] < delay*2+delayTolerance) { - code = code << 1; - } else if (RCSwitch::timings[i] > delay*2-delayTolerance && RCSwitch::timings[i] < delay*2+delayTolerance && RCSwitch::timings[i+1] > delay-delayTolerance && RCSwitch::timings[i+1] < delay+delayTolerance) { - code+=1; - code = code << 1; - } else { - // Failed - i = changeCount; - code = 0; - } - } - code = code >> 1; - if (changeCount > 6) { // ignore < 4bit values as there are no devices sending 4bit values => noise - RCSwitch::nReceivedValue = code; - RCSwitch::nReceivedBitlength = changeCount / 2; - RCSwitch::nReceivedDelay = delay; - RCSwitch::nReceivedProtocol = 2; + if (changeCount > 7) { // ignore very short transmissions: no device sends them, so this must be noise + RCSwitch::nReceivedValue = code; + RCSwitch::nReceivedBitlength = (changeCount - 1) / 2; + RCSwitch::nReceivedDelay = delay; + RCSwitch::nReceivedProtocol = p; + return true; } - if (code == 0){ - return false; - }else if (code != 0){ - return true; - } - -} - -void RCSwitch::handleInterrupt() { - - static unsigned int duration; - static unsigned int changeCount; - static unsigned long lastTime; - static unsigned int repeatCount; - - long time = micros(); - duration = time - lastTime; - - if (duration > 5000 && duration > RCSwitch::timings[0] - 200 && duration < RCSwitch::timings[0] + 200) { - repeatCount++; - changeCount--; - - if (repeatCount == 2) { - if (receiveProtocol1(changeCount) == false){ - if (receiveProtocol2(changeCount) == false){ - //failed - } - } - repeatCount = 0; + return false; +} + +void RECEIVE_ATTR RCSwitch::handleInterrupt() { + + static unsigned int changeCount = 0; + static unsigned long lastTime = 0; + static unsigned int repeatCount = 0; + + const long time = micros(); + const unsigned int duration = time - lastTime; + + if (duration > RCSwitch::nSeparationLimit) { + // A long stretch without signal level change occurred. This could + // be the gap between two transmission. + if (diff(duration, RCSwitch::timings[0]) < 200) { + // This long signal is close in length to the long signal which + // started the previously recorded timings; this suggests that + // it may indeed by a a gap between two transmissions (we assume + // here that a sender will send the signal multiple times, + // with roughly the same gap between them). + repeatCount++; + if (repeatCount == 2) { + for(unsigned int i = 1; i <= numProto; i++) { + if (receiveProtocol(i, changeCount)) { + // receive succeeded for protocol i + break; + } + } + repeatCount = 0; + } } changeCount = 0; - } else if (duration > 5000) { - changeCount = 0; } - + + // detect overflow if (changeCount >= RCSWITCH_MAX_CHANGES) { changeCount = 0; repeatCount = 0; } + RCSwitch::timings[changeCount++] = duration; lastTime = time; } - -/** - * Turns a decimal value to its binary representation - */ -char* RCSwitch::dec2binWzerofill(unsigned long Dec, unsigned int bitLength){ - static char bin[64]; - unsigned int i=0; - - while (Dec > 0) { - bin[32+i++] = ((Dec & 1) > 0) ? '1' : '0'; - Dec = Dec >> 1; - } - - for (unsigned int j = 0; j< bitLength; j++) { - if (j >= bitLength - i) { - bin[j] = bin[ 31 + i - (j - (bitLength - i)) ]; - }else { - bin[j] = '0'; - } - } - bin[bitLength] = '\0'; - - return bin; -} - +#endif diff --git a/RFSource/RCSwitch.h b/RFSource/RCSwitch.h old mode 100644 new mode 100755 index d34d1f9..27e44ec --- a/RFSource/RCSwitch.h +++ b/RFSource/RCSwitch.h @@ -1,12 +1,17 @@ /* RCSwitch - Arduino libary for remote control outlet switches - Copyright (c) 2011 Suat Özgür. All right reserved. + Copyright (c) 2011 Suat Özgür. All right reserved. Contributors: - Andre Koehler / info(at)tomate-online(dot)de - Gordeev Andrey Vladimirovich / gordeev(at)openpyro(dot)com + - Skineffect / http://forum.ardumote.com/viewtopic.php?f=2&t=46 + - Dominik Fischer / dom_fischer(at)web(dot)de + - Frank Oltmanns / .(at)gmail(dot)com + - Max Horn / max(at)quendi(dot)de + - Robert ter Vehn / .(at)gmail(dot)com - Project home: http://code.google.com/p/rc-switch/ + Project home: https://github.com/sui77/rc-switch/ This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -27,30 +32,34 @@ #if defined(ARDUINO) && ARDUINO >= 100 #include "Arduino.h" -#else +#elif defined(ENERGIA) // LaunchPad, FraunchPad and StellarPad specific + #include "Energia.h" +#elif defined(RPI) // Raspberry Pi + #define RaspberryPi + + // Include libraries for RPi: + #include /* memcpy */ + #include /* abs */ #include - #include - #define NULL 0 - #define CHANGE 1 -#ifdef __cplusplus -extern "C"{ +#elif defined(SPARK) + #include "application.h" +#else + #include "WProgram.h" #endif -typedef uint8_t boolean; -typedef uint8_t byte; -#if !defined(NULL) -#endif -#ifdef __cplusplus -} -#endif -#endif +#include + +// At least for the ATTiny X4/X5, receiving has to be disabled due to +// missing libm depencies (udivmodhi4) +#if defined( __AVR_ATtinyX5__ ) or defined ( __AVR_ATtinyX4__ ) +#define RCSwitchDisableReceiving +#endif // Number of maximum High/Low changes per packet. // We can handle up to (unsigned long) => 32 bit * 2 H/L changes per bit + 2 for sync #define RCSWITCH_MAX_CHANGES 67 - class RCSwitch { public: @@ -58,64 +67,88 @@ class RCSwitch { void switchOn(int nGroupNumber, int nSwitchNumber); void switchOff(int nGroupNumber, int nSwitchNumber); - void switchOn(char* sGroup, int nSwitchNumber); - void switchOff(char* sGroup, int nSwitchNumber); + void switchOn(const char* sGroup, int nSwitchNumber); + void switchOff(const char* sGroup, int nSwitchNumber); void switchOn(char sFamily, int nGroup, int nDevice); void switchOff(char sFamily, int nGroup, int nDevice); - - void sendTriState(char* Code); - void send(unsigned long Code, unsigned int length); - void send(char* Code); + void switchOn(const char* sGroup, const char* sDevice); + void switchOff(const char* sGroup, const char* sDevice); + void switchOn(char sGroup, int nDevice); + void switchOff(char sGroup, int nDevice); + + void sendTriState(const char* sCodeWord); + void send(unsigned long code, unsigned int length); + void send(const char* sCodeWord); + #if not defined( RCSwitchDisableReceiving ) void enableReceive(int interrupt); void enableReceive(); void disableReceive(); bool available(); - void resetAvailable(); - + void resetAvailable(); + unsigned long getReceivedValue(); unsigned int getReceivedBitlength(); unsigned int getReceivedDelay(); - unsigned int getReceivedProtocol(); + unsigned int getReceivedProtocol(); unsigned int* getReceivedRawdata(); + #endif void enableTransmit(int nTransmitterPin); void disableTransmit(); void setPulseLength(int nPulseLength); void setRepeatTransmit(int nRepeatTransmit); + #if not defined( RCSwitchDisableReceiving ) void setReceiveTolerance(int nPercent); - void setProtocol(int nProtocol); - void setProtocol(int nProtocol, int nPulseLength); - + #endif + + struct HighLow { + uint8_t high; + uint8_t low; + }; + + struct Protocol { + int pulseLength; + HighLow syncFactor; + HighLow zero; + HighLow one; + /** @brief if true inverts the high and low logic levels in the HighLow structs */ + bool invertedSignal; + }; + + void setProtocol(Protocol protocol); + void setProtocol(int nProtocol); + void setProtocol(int nProtocol, int nPulseLength); + private: - char* getCodeWordB(int nGroupNumber, int nSwitchNumber, boolean bStatus); - char* getCodeWordA(char* sGroup, int nSwitchNumber, boolean bStatus); - char* getCodeWordC(char sFamily, int nGroup, int nDevice, boolean bStatus); - void sendT0(); - void sendT1(); - void sendTF(); - void send0(); - void send1(); - void sendSync(); - void transmit(int nHighPulses, int nLowPulses); - - static char* dec2binWzerofill(unsigned long dec, unsigned int length); - + char* getCodeWordA(const char* sGroup, const char* sDevice, bool bStatus); + char* getCodeWordB(int nGroupNumber, int nSwitchNumber, bool bStatus); + char* getCodeWordC(char sFamily, int nGroup, int nDevice, bool bStatus); + char* getCodeWordD(char group, int nDevice, bool bStatus); + void transmit(HighLow pulses); + + #if not defined( RCSwitchDisableReceiving ) static void handleInterrupt(); - static bool receiveProtocol1(unsigned int changeCount); - static bool receiveProtocol2(unsigned int changeCount); + static bool receiveProtocol(const int p, unsigned int changeCount); int nReceiverInterrupt; + #endif int nTransmitterPin; - int nPulseLength; int nRepeatTransmit; - char nProtocol; + + Protocol protocol; - static int nReceiveTolerance; + #if not defined( RCSwitchDisableReceiving ) + static int nReceiveTolerance; static unsigned long nReceivedValue; static unsigned int nReceivedBitlength; - static unsigned int nReceivedDelay; - static unsigned int nReceivedProtocol; + static unsigned int nReceivedDelay; + static unsigned int nReceivedProtocol; + const static unsigned int nSeparationLimit; + /* + * timings[0] contains sync timing, followed by a number of bits + */ static unsigned int timings[RCSWITCH_MAX_CHANGES]; + #endif }; diff --git a/RFSource/RCSwitch.o b/RFSource/RCSwitch.o deleted file mode 100644 index 7d88b96..0000000 Binary files a/RFSource/RCSwitch.o and /dev/null differ diff --git a/RFSource/RFOutlet.c b/RFSource/RFOutlet.c new file mode 100755 index 0000000..47b4fe4 --- /dev/null +++ b/RFSource/RFOutlet.c @@ -0,0 +1,26 @@ +#include +#include +#include + +bool parseStringToInt(const char *input, int *value, int base) { + bool success = true; + char *endptr; + errno = 0; + long longNumber = strtol(input, &endptr, base); + + if (longNumber < INT_MIN || INT_MAX < longNumber) { + errno = ERANGE; + } + + if (*endptr == longNumber || *endptr != '\0') { + errno = EINVAL; + } + + if (errno != 0) { + success = false; + } else { + *value = longNumber; + } + + return success; +} diff --git a/RFSource/RFOutlet.h b/RFSource/RFOutlet.h new file mode 100755 index 0000000..7a544ac --- /dev/null +++ b/RFSource/RFOutlet.h @@ -0,0 +1 @@ +bool parseStringToInt(const char *input, int *value, int base); diff --git a/RFSource/RFSniffer.cpp b/RFSource/RFSniffer.cpp deleted file mode 100644 index 6298526..0000000 --- a/RFSource/RFSniffer.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "RCSwitch.h" -#include -#include - - -RCSwitch mySwitch; - - - -int main(int argc, char *argv[]) { - - // This pin is not the first pin on the RPi GPIO header! - // Consult https://projects.drogon.net/raspberry-pi/wiringpi/pins/ - // for more information. - int PIN = 2; - - if(wiringPiSetup() == -1) - return 0; - - mySwitch = RCSwitch(); - mySwitch.enableReceive(PIN); // Receiver on inerrupt 0 => that is pin #2 - - - while(1) { - - if (mySwitch.available()) { - - int value = mySwitch.getReceivedValue(); - - if (value == 0) { - printf("Unknown encoding"); - } else { - printf("Received %i\n", mySwitch.getReceivedValue() ); - //Show pulse(Depends on your RF outlet device. You may need to change the pulse on codesend.cpp) - printf("Received pulse %i\n", mySwitch.getReceivedDelay() ); - } - - mySwitch.resetAvailable(); - - } - - - } - - exit(0); - - -} diff --git a/RFSource/RFSniffer.o b/RFSource/RFSniffer.o deleted file mode 100644 index 4c9f250..0000000 Binary files a/RFSource/RFSniffer.o and /dev/null differ diff --git a/RFSource/codesend b/RFSource/codesend new file mode 100755 index 0000000..89e7fdf Binary files /dev/null and b/RFSource/codesend differ diff --git a/RFSource/codesend.cpp b/RFSource/codesend.cpp old mode 100644 new mode 100755 index c051cb2..35234b8 --- a/RFSource/codesend.cpp +++ b/RFSource/codesend.cpp @@ -1,17 +1,44 @@ #include #include #include +#include +#include #include "RCSwitch.h" +#include "RFOutlet.h" +// For serializing access to hardware when called simultaneously +#include "shared_mutex.h" #define DEFAULT_PIN 0 #define DEFAULT_PULSE_LENGTH 189 +#define DEFAULT_HW_LOCK_TIMEOUT 2 + +shared_mutex_t mutex; void printUsage(char *argv[]) { printf("Usage: %s [-p (default: %i)] [-l (default: %i)].\n", argv[0], DEFAULT_PIN, DEFAULT_PULSE_LENGTH); } +void *interruptThread(void *signal) { + printf("Shutting down...\n"); + // Give the locking mechanism some time and then exit + sleep(DEFAULT_HW_LOCK_TIMEOUT + 1); + exit(*(int *)signal); +} + +void interruptHandler(int signal) { + pthread_t thread; + if (pthread_create(&thread, NULL, interruptThread, &signal) != 0) { + exit(signal); + } +} + int main(int argc, char *argv[]) { + signal(SIGINT, interruptHandler); + signal(SIGHUP, interruptHandler); + signal(SIGQUIT, interruptHandler); + signal(SIGABRT, interruptHandler); + int i; char * argumentPIN = NULL; @@ -42,7 +69,10 @@ int main(int argc, char *argv[]) { int PIN = DEFAULT_PIN; if (argumentPIN != NULL) { - PIN = atoi(argumentPIN); + if (!parseStringToInt(argumentPIN, &PIN, 10)) { + perror("Invalid PIN"); + return EXIT_FAILURE; + } } /* Now set the values of "argc" and "argv" to the values after the @@ -57,7 +87,42 @@ int main(int argc, char *argv[]) { argv += optind; // Parse the first parameter to this command as an integer - int code = atoi(argv[0]); + int code = 0; + + if (!parseStringToInt(argv[0], &code, 10)) { + perror("Invalid code"); + return EXIT_FAILURE; + } + + // Acquire the shared mutex and set a permissive mode since codesend may be executed by www-data and other terminal users + mutex = shared_mutex_init("codesend_mutex", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + + if (mutex.ptr == NULL) { + return EXIT_FAILURE; + } + + // Timeout for acquiring the lock, so we don't hang the process! + struct timespec abs_time; + clock_gettime(CLOCK_REALTIME , &abs_time); + abs_time.tv_sec += DEFAULT_HW_LOCK_TIMEOUT; + + if (pthread_mutex_timedlock(mutex.ptr, &abs_time) != 0) { + const char * const fd_path = "/proc/self/fd"; + const char *shm_path = NULL; + size_t needed = snprintf(NULL, 0, "%s/%d", fd_path, mutex.shm_fd) + 1; + char *buffer = (char *)malloc(needed); + if (snprintf(buffer, needed, "%s/%d", fd_path, mutex.shm_fd) >= 0) { + shm_path = realpath(buffer, NULL); + } + free(buffer); + + if (shm_path == NULL) { + shm_path = "/dev/shm"; + } + + fprintf(stderr, "Could not acquire an exclusive lock to activate the hardware, please try deleting the lock file at '%s' and try again\n", shm_path); + return EXIT_FAILURE; + } if (wiringPiSetup () == -1) { return EXIT_FAILURE; @@ -67,7 +132,10 @@ int main(int argc, char *argv[]) { int pulseLength = DEFAULT_PULSE_LENGTH; if (argumentPulseLength != NULL) { - pulseLength = atoi(argumentPulseLength); + if (!parseStringToInt(argumentPulseLength, &pulseLength, 10)) { + perror("Invalid pulse length"); + return EXIT_FAILURE; + } } printf("Sending Code: %i. PIN: %i. Pulse Length: %i\n", code, PIN, pulseLength); @@ -76,6 +144,13 @@ int main(int argc, char *argv[]) { mySwitch.setPulseLength(pulseLength); mySwitch.enableTransmit(PIN); mySwitch.send(code, 24); + mySwitch.disableTransmit(); + + pthread_mutex_unlock(mutex.ptr); + if (shared_mutex_close(mutex)) { + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } diff --git a/RFSource/codesend.o b/RFSource/codesend.o deleted file mode 100644 index fa50a0c..0000000 Binary files a/RFSource/codesend.o and /dev/null differ diff --git a/RFSource/send.cpp b/RFSource/send.cpp deleted file mode 100644 index 076e77c..0000000 --- a/RFSource/send.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - Usage: ./send - Command is 0 for OFF and 1 for ON - */ - -#include "RCSwitch.h" -#include -#include - -int main(int argc, char *argv[]) { - - /* - output PIN is hardcoded for testing purposes - see https://projects.drogon.net/raspberry-pi/wiringpi/pins/ - for pin mapping of the raspberry pi GPIO connector - */ - int PIN = 0; - char* systemCode = argv[1]; - int unitCode = atoi(argv[2]); - int command = atoi(argv[3]); - - if (wiringPiSetup () == -1) return 1; - printf("sending systemCode[%s] unitCode[%i] command[%i]\n", systemCode, unitCode, command); - RCSwitch mySwitch = RCSwitch(); - mySwitch.enableTransmit(PIN); - - switch(command) { - case 1: - mySwitch.switchOn(systemCode, unitCode); - break; - case 0: - mySwitch.switchOff(systemCode, unitCode); - break; - default: - printf("command[%i] is unsupported\n", command); - return -1; - } - return 0; -} diff --git a/RFSource/shared_mutex.c b/RFSource/shared_mutex.c new file mode 100755 index 0000000..0d1a1fe --- /dev/null +++ b/RFSource/shared_mutex.c @@ -0,0 +1,114 @@ +#include "shared_mutex.h" +#include // errno, ENOENT +#include // O_RDWR, O_CREATE +#include // NAME_MAX +#include // shm_open, shm_unlink, mmap, munmap, + // PROT_READ, PROT_WRITE, MAP_SHARED, MAP_FAILED +#include // ftruncate, close +#include // perror +#include // malloc, free +#include // strcpy + +shared_mutex_t shared_mutex_init(const char *name, mode_t mode) { + shared_mutex_t mutex = {NULL, 0, NULL, 0}; + errno = 0; + + // Open existing shared memory object, or create one. + // Two separate calls are needed here, to mark fact of creation + // for later initialization of pthread mutex. + mutex.shm_fd = shm_open(name, O_RDWR, mode); + if (errno == ENOENT) { + mutex.shm_fd = shm_open(name, O_RDWR|O_CREAT, mode); + mutex.created = 1; + // Change permissions of shared memory, so every body can access it. Avoiding the umask of shm_open + if (fchmod(mutex.shm_fd, mode) != 0) { + perror("fchmod"); + } + } + if (mutex.shm_fd == -1) { + perror("shm_open"); + return mutex; + } + + // Truncate shared memory segment so it would contain + // pthread_mutex_t. + if (ftruncate(mutex.shm_fd, sizeof(pthread_mutex_t)) != 0) { + perror("ftruncate"); + return mutex; + } + + // Map pthread mutex into the shared memory. + void *addr = mmap( + NULL, + sizeof(pthread_mutex_t), + PROT_READ|PROT_WRITE, + MAP_SHARED, + mutex.shm_fd, + 0 + ); + if (addr == MAP_FAILED) { + perror("mmap"); + return mutex; + } + pthread_mutex_t *mutex_ptr = (pthread_mutex_t *)addr; + + // If shared memory was just initialized - + // initialize the mutex as well. + if (mutex.created) { + pthread_mutexattr_t attr; + if (pthread_mutexattr_init(&attr)) { + perror("pthread_mutexattr_init"); + return mutex; + } + if (pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED)) { + perror("pthread_mutexattr_setpshared"); + return mutex; + } + if (pthread_mutex_init(mutex_ptr, &attr)) { + perror("pthread_mutex_init"); + return mutex; + } + } + mutex.ptr = mutex_ptr; + mutex.name = (char *)malloc(NAME_MAX+1); + strcpy(mutex.name, name); + return mutex; +} + +int shared_mutex_close(shared_mutex_t mutex) { + if (munmap((void *)mutex.ptr, sizeof(pthread_mutex_t))) { + perror("munmap"); + return -1; + } + mutex.ptr = NULL; + if (close(mutex.shm_fd)) { + perror("close"); + return -1; + } + mutex.shm_fd = 0; + free(mutex.name); + return 0; +} + +int shared_mutex_destroy(shared_mutex_t mutex) { + if ((errno = pthread_mutex_destroy(mutex.ptr))) { + perror("pthread_mutex_destroy"); + return -1; + } + if (munmap((void *)mutex.ptr, sizeof(pthread_mutex_t))) { + perror("munmap"); + return -1; + } + mutex.ptr = NULL; + if (close(mutex.shm_fd)) { + perror("close"); + return -1; + } + mutex.shm_fd = 0; + if (shm_unlink(mutex.name)) { + perror("shm_unlink"); + return -1; + } + free(mutex.name); + return 0; +} diff --git a/RFSource/shared_mutex.h b/RFSource/shared_mutex.h new file mode 100755 index 0000000..b700400 --- /dev/null +++ b/RFSource/shared_mutex.h @@ -0,0 +1,67 @@ +#ifndef SHARED_MUTEX_H +#define SHARED_MUTEX_H + +#include + +#include // pthread_mutex_t, pthread_mutexattr_t, + // pthread_mutexattr_init, pthread_mutexattr_setpshared, + // pthread_mutex_init, pthread_mutex_destroy + +// Structure of a shared mutex. +typedef struct shared_mutex_t { + pthread_mutex_t *ptr; // Pointer to the pthread mutex and + // shared memory segment. + int shm_fd; // Descriptor of shared memory object. + char* name; // Name of the mutex and associated + // shared memory object. + int created; // Equals 1 (true) if initialization + // of this structure caused creation + // of a new shared mutex. + // Equals 0 (false) if this mutex was + // just retrieved from shared memory. +} shared_mutex_t; + +// Initialize a new shared mutex with given `name`. If a mutex +// with such name exists in the system, it will be loaded. +// Otherwise a new mutes will by created. +// +// In case of any error, it will be printed into the standard output +// and the returned structure will have `ptr` equal `NULL`. +// `errno` wil not be reset in such case, so you may used it. +// +// **NOTE:** In case when the mutex appears to be uncreated, +// this function becomes *non-thread-safe*. If multiple threads +// call it at one moment, there occur several race conditions, +// in which one call might recreate another's shared memory +// object or rewrite another's pthread mutex in the shared memory. +// There is no workaround currently, except to run first +// initialization only before multi-threaded or multi-process +// functionality. +shared_mutex_t shared_mutex_init(const char *name, mode_t mode); + +// Close access to the shared mutex and free all the resources, +// used by the structure. +// +// Returns 0 in case of success. If any error occurs, it will be +// printed into the standard output and the function will return -1. +// `errno` wil not be reset in such case, so you may used it. +// +// **NOTE:** It will not destroy the mutex. The mutex would not +// only be available to other processes using it right now, +// but also to any process which might want to use it later on. +// For complete desctruction use `shared_mutex_destroy` instead. +// +// **NOTE:** It will not unlock locked mutex. +int shared_mutex_close(shared_mutex_t mutex); + +// Close and destroy shared mutex. +// Any open pointers to it will be invalidated. +// +// Returns 0 in case of success. If any error occurs, it will be +// printed into the standard output and the function will return -1. +// `errno` wil not be reset in such case, so you may used it. +// +// **NOTE:** It will not unlock locked mutex. +int shared_mutex_destroy(shared_mutex_t mutex); + +#endif // SHARED_MUTEX_H diff --git a/RFSource/sniffer b/RFSource/sniffer new file mode 100755 index 0000000..5ca4674 Binary files /dev/null and b/RFSource/sniffer differ diff --git a/RFSource/sniffer.cpp b/RFSource/sniffer.cpp new file mode 100755 index 0000000..c909676 --- /dev/null +++ b/RFSource/sniffer.cpp @@ -0,0 +1,73 @@ +#include +#include +#include +#include + +#include "RFOutlet.h" +#include "RCSwitch.h" + +#define DEFAULT_PIN 2 + +void printUsage(char *argv[]) { + printf("Usage: %s [-p (default: %i)].\n", argv[0], DEFAULT_PIN); +} + +int main(int argc, char *argv[]) { + char *argumentPIN = NULL; + + int c; + while ((c = getopt(argc, argv, "p:")) != -1) { + switch (c) { + case 'p': + argumentPIN = optarg; + break; + + case '?': + default: + printUsage(argv); + exit(EXIT_FAILURE); + break; + } + } + + // This PIN is not the first PIN on the Raspberry Pi GPIO header! + // Consult https://projects.drogon.net/raspberry-pi/wiringpi/pins/ + // for more information. + int PIN = DEFAULT_PIN; + + if (argumentPIN != NULL) { + if (!parseStringToInt(argumentPIN, &PIN, 10)) { + perror("Invalid PIN"); + return EXIT_FAILURE; + } + printf("Listening on PIN: %i\n", PIN); + } else { + printUsage(argv); + return EXIT_FAILURE; + } + + if (wiringPiSetup() == -1) { + return EXIT_FAILURE; + } + + RCSwitch mySwitch = RCSwitch(); + mySwitch.enableReceive(PIN); // Receiver on INTERRUPT 0 => that is pin #2 + + while(true) { + if (mySwitch.available()) { + int value = mySwitch.getReceivedValue(); + + if (value == 0) { + printf("Unknown encoding"); + } else { + printf("Received %i\n", mySwitch.getReceivedValue() ); + // Show pulse(Depends on your RF outlet device. You may need to change the pulse on codesend.cpp) + printf("Received pulse %i\n", mySwitch.getReceivedDelay() ); + } + + mySwitch.resetAvailable(); + } + } + + return EXIT_SUCCESS; +} diff --git a/codesend b/codesend index 0f40b5e..89e7fdf 100755 Binary files a/codesend and b/codesend differ diff --git a/configuration.inc.php.sample b/configuration.inc.php.sample new file mode 100644 index 0000000..008ad5b --- /dev/null +++ b/configuration.inc.php.sample @@ -0,0 +1,55 @@ + array( + "on" => 349491, + "off" => 349500 + ), + "2" => array( + "on" => 349635, + "off" => 349644 + ), + "3" => array( + "on" => 349955, + "off" => 349964 + ), + "4" => array( + "on" => 351491, + "off" => 351500 + ), + "5" => array( + "on" => 357635, + "off" => 357644 + ), + "6" => array( + "on" => 349491, + "off" => 349500 + ), + "7" => array( + "on" => 349635, + "off" => 349644 + ), + "8" => array( + "on" => 349955, + "off" => 349964 + ), + "9" => array( + "on" => 351491, + "off" => 351500 + ), + "10" => array( + "on" => 357635, + "off" => 357644 + ) +); + +// Path to the codesend binary (current directory is the default) +$codeSendPath = './codesend'; + +// This PIN is not the first PIN on the Raspberry Pi GPIO header! +// Consult https://projects.drogon.net/raspberry-pi/wiringpi/pins/ +// for more information. +$codeSendPIN = "0"; + +// Pulse length depends on the RF outlets you are using. Use RFSniffer to see what pulse length your device uses. +$codeSendPulseLength = "189"; diff --git a/homebridge-http.config.json.sample b/homebridge-http.config.json.sample new file mode 100644 index 0000000..0da3f6c --- /dev/null +++ b/homebridge-http.config.json.sample @@ -0,0 +1,133 @@ +{ + "bridge": { + "name": "HomeBridge", + "username": "CD:43:8A:C5:CD:42", + "port": 51826, + "pin": "124-45-678" + }, + + "description": "HomeBridge HTTP Status Control", + + "accessories": [ + { + "accessory": "Http", + "name": "Outlet 1", + "switchHandling": "realtime", + "http_method": "GET", + "on_url": "http://localhost/rfoutlet/toggle.php?outletId=1&outletStatus=on", + "off_url": "http://localhost/rfoutlet/toggle.php?outletId=1&outletStatus=off", + "status_url": "http://localhost/rfoutlet/status.php?outletId=1", + "service": "Light", + "brightnessHandling": "no", + "sendimmediately": "" + }, + { + "accessory": "Http", + "name": "Outlet 2", + "switchHandling": "realtime", + "http_method": "GET", + "on_url": "http://localhost/rfoutlet/toggle.php?outletId=2&outletStatus=on", + "off_url": "http://localhost/rfoutlet/toggle.php?outletId=2&outletStatus=off", + "status_url": "http://localhost/rfoutlet/status.php?outletId=2", + "service": "Light", + "brightnessHandling": "no", + "sendimmediately": "" + }, + { + "accessory": "Http", + "name": "Outlet 3", + "switchHandling": "realtime", + "http_method": "GET", + "on_url": "http://localhost/rfoutlet/toggle.php?outletId=3&outletStatus=on", + "off_url": "http://localhost/rfoutlet/toggle.php?outletId=3&outletStatus=off", + "status_url": "http://localhost/rfoutlet/status.php?outletId=3", + "service": "Light", + "brightnessHandling": "no", + "sendimmediately": "" + }, + { + "accessory": "Http", + "name": "Outlet 4", + "switchHandling": "realtime", + "http_method": "GET", + "on_url": "http://localhost/rfoutlet/toggle.php?outletId=4&outletStatus=on", + "off_url": "http://localhost/rfoutlet/toggle.php?outletId=4&outletStatus=off", + "status_url": "http://localhost/rfoutlet/status.php?outletId=4", + "service": "Light", + "brightnessHandling": "no", + "sendimmediately": "" + }, + { + "accessory": "Http", + "name": "Outlet 5", + "switchHandling": "realtime", + "http_method": "GET", + "on_url": "http://localhost/rfoutlet/toggle.php?outletId=5&outletStatus=on", + "off_url": "http://localhost/rfoutlet/toggle.php?outletId=5&outletStatus=off", + "status_url": "http://localhost/rfoutlet/status.php?outletId=5", + "service": "Light", + "brightnessHandling": "no", + "sendimmediately": "" + }, + { + "accessory": "Http", + "name": "Outlet 6", + "switchHandling": "realtime", + "http_method": "GET", + "on_url": "http://localhost/rfoutlet/toggle.php?outletId=6&outletStatus=on", + "off_url": "http://localhost/rfoutlet/toggle.php?outletId=6&outletStatus=off", + "status_url": "http://localhost/rfoutlet/status.php?outletId=6", + "service": "Light", + "brightnessHandling": "no", + "sendimmediately": "" + }, + { + "accessory": "Http", + "name": "Outlet 7", + "switchHandling": "realtime", + "http_method": "GET", + "on_url": "http://localhost/rfoutlet/toggle.php?outletId=7&outletStatus=on", + "off_url": "http://localhost/rfoutlet/toggle.php?outletId=7&outletStatus=off", + "status_url": "http://localhost/rfoutlet/status.php?outletId=7", + "service": "Light", + "brightnessHandling": "no", + "sendimmediately": "" + }, + { + "accessory": "Http", + "name": "Outlet 8", + "switchHandling": "realtime", + "http_method": "GET", + "on_url": "http://localhost/rfoutlet/toggle.php?outletId=8&outletStatus=on", + "off_url": "http://localhost/rfoutlet/toggle.php?outletId=8&outletStatus=off", + "status_url": "http://localhost/rfoutlet/status.php?outletId=8", + "service": "Light", + "brightnessHandling": "no", + "sendimmediately": "" + }, + { + "accessory": "Http", + "name": "Outlet 9", + "switchHandling": "realtime", + "http_method": "GET", + "on_url": "http://localhost/rfoutlet/toggle.php?outletId=9&outletStatus=on", + "off_url": "http://localhost/rfoutlet/toggle.php?outletId=9&outletStatus=off", + "status_url": "http://localhost/rfoutlet/status.php?outletId=9", + "service": "Light", + "brightnessHandling": "no", + "sendimmediately": "" + }, + { + "accessory": "Http", + "name": "Outlet 10", + "switchHandling": "realtime", + "http_method": "GET", + "on_url": "http://localhost/rfoutlet/toggle.php?outletId=10&outletStatus=on", + "off_url": "http://localhost/rfoutlet/toggle.php?outletId=10&outletStatus=off", + "status_url": "http://localhost/rfoutlet/status.php?outletId=10", + "service": "Light", + "brightnessHandling": "no", + "sendimmediately": "" + } + ] +} diff --git a/index.html b/index.html old mode 100644 new mode 100755 index 190fb92..a47e3fe --- a/index.html +++ b/index.html @@ -79,7 +79,7 @@ - +
@@ -88,6 +88,54 @@
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ diff --git a/script.js b/script.js old mode 100644 new mode 100755 diff --git a/sniffer b/sniffer new file mode 100755 index 0000000..5ca4674 Binary files /dev/null and b/sniffer differ diff --git a/status.php b/status.php new file mode 100755 index 0000000..8cdfb11 --- /dev/null +++ b/status.php @@ -0,0 +1,15 @@ + array( - "on" => 349491, - "off" => 349500 - ), - "2" => array( - "on" => 349635, - "off" => 349644 - ), - "3" => array( - "on" => 349955, - "off" => 349964 - ), - "4" => array( - "on" => 351491, - "off" => 351500 - ), - "5" => array( - "on" => 357635, - "off" => 357644 - ), -); - -// Path to the codesend binary (current directory is the default) -$codeSendPath = './codesend'; - -// This PIN is not the first PIN on the Raspberry Pi GPIO header! -// Consult https://projects.drogon.net/raspberry-pi/wiringpi/pins/ -// for more information. -$codeSendPIN = "0"; - -// Pulse length depends on the RF outlets you are using. Use RFSniffer to see what pulse length your device uses. -$codeSendPulseLength = "189"; +// Please edit this file with your outlet codes and pulse length +require_once('configuration.inc.php'); if (!file_exists($codeSendPath)) { error_log("$codeSendPath is missing, please edit the script", 0); die(json_encode(array('success' => false))); } -$outletLight = $_POST['outletId']; -$outletStatus = $_POST['outletStatus']; +$outletToToggle = (!empty($_POST['outletId'])) ? $_POST['outletId'] : $_GET['outletId']; +$outletStatus = (!empty($_POST['outletStatus'])) ? $_POST['outletStatus'] : $_GET['outletStatus']; -if ($outletLight == "6") { - // 6 is all 5 outlets combined +if (empty($outletToToggle) || empty($outletStatus)) { + error_log('Missing POST paremeters', 0); + die(json_encode(array('success' => false))); +} + +// Add check to see if outlet to toggle is in our codes or not + +if ($outletToToggle == "all") { + // all is every outlets combined if (function_exists('array_column')) { // PHP >= 5.5 $codesToToggle = array_column($codes, $outletStatus); @@ -58,13 +33,46 @@ } } else { // One - $codesToToggle = array($codes[$outletLight][$outletStatus]); + $codesToToggle = array($codes[$outletToToggle][$outletStatus]); } +$returnCode = 0; +$output = NULL; foreach ($codesToToggle as $codeSendCode) { - shell_exec($codeSendPath . ' ' . $codeSendCode . ' -p ' . $codeSendPIN . ' -l ' . $codeSendPulseLength); - sleep(1); + exec($codeSendPath . ' ' . $codeSendCode . ' -p ' . $codeSendPIN . ' -l ' . $codeSendPulseLength, $output, $returnCode); + if ($returnCode != 0) { + error_log("Failed to execute $codeSendPath. Output was: " . implode(", ", $output) . ". Code $returnCode"); + die(json_encode(array('success' => false, 'output' => $output))); + } +} + +$status_contents = file_get_contents('status.json'); + +$statuses = NULL; + +if ($status_contents === FALSE || $outletToToggle == "all") { + $defaultStatus = 0; + if ($outletToToggle == "all") { + $defaultStatus = $outletStatus == "on" ? 1 : 0; + } + + $statuses = array(); + + foreach ($codes as $outletNumber=>$information) { + $statuses[$outletNumber] = $defaultStatus; + } +} else { + $statuses = json_decode($status_contents, true); +} + +if ($outletToToggle != "all") { + $statuses[$outletToToggle] = $outletStatus == "on" ? 1 : 0; +} + +$JSON = json_encode($statuses, JSON_PRETTY_PRINT); + +if (file_put_contents('status.json', $JSON) === FALSE) { + die(json_encode(array('success' => false, 'output' => "file_put_contents"))); } die(json_encode(array('success' => true))); -?>