From ffb70b5c4106772bf8a8c23b0c70fc61876c0c7f Mon Sep 17 00:00:00 2001 From: faguiard Date: Fri, 19 May 2017 23:21:28 +0200 Subject: [PATCH 1/3] Embed RCSwitch library to use with 433MHz RF simple link kit --- .gitignore | 9 +- .../Source/v1.2/grove_pi_v1_2_7/RCSwitch.cpp | 804 ++++++++++++++++++ .../Source/v1.2/grove_pi_v1_2_7/RCSwitch.h | 163 ++++ .../Source/v1.2/grove_pi_v1_2_7/README.md | 2 +- .../v1.2/grove_pi_v1_2_7/grove_pi_v1_2_7.ino | 558 +++++++++++- .../433rf_link_kit.png | Bin 0 -> 64461 bytes .../grove_rflink433mhz_rcswitch/README.md | 25 + .../grove_433mhz_rx_rcswitch.py | 301 +++++++ .../grove_433mhz_rx_rcswitch_example.py | 19 + .../grove_433mhz_tx_rcswitch.py | 220 +++++ .../grove_433mhz_tx_rcswitch_example.py | 59 ++ Software/Python/grovepi.py | 5 + Software/Python/test.py | 16 + 13 files changed, 2178 insertions(+), 3 deletions(-) create mode 100644 Firmware/Source/v1.2/grove_pi_v1_2_7/RCSwitch.cpp create mode 100644 Firmware/Source/v1.2/grove_pi_v1_2_7/RCSwitch.h create mode 100644 Software/Python/grove_rflink433mhz_rcswitch/433rf_link_kit.png create mode 100644 Software/Python/grove_rflink433mhz_rcswitch/README.md create mode 100644 Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch.py create mode 100644 Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch_example.py create mode 100644 Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_tx_rcswitch.py create mode 100644 Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_tx_rcswitch_example.py create mode 100644 Software/Python/test.py diff --git a/.gitignore b/.gitignore index af8a65a8..64dbe123 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,11 @@ Release # Visual Studio files *.pyc -*.pfx \ No newline at end of file +*.pfx + +# Jetbrains files +.idea + +# CMake files +cmake*/ +CMakeLists.txt diff --git a/Firmware/Source/v1.2/grove_pi_v1_2_7/RCSwitch.cpp b/Firmware/Source/v1.2/grove_pi_v1_2_7/RCSwitch.cpp new file mode 100644 index 00000000..2c972001 --- /dev/null +++ b/Firmware/Source/v1.2/grove_pi_v1_2_7/RCSwitch.cpp @@ -0,0 +1,804 @@ +/* + RCSwitch - Arduino libary for remote control outlet switches + 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 + - 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: 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 + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "RCSwitch.h" + +#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; +int RCSwitch::nReceiveTolerance = 60; +const unsigned int RCSwitch::nSeparationLimit = 4600; +// 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->nTransmitterPin = -1; + this->setRepeatTransmit(10); + 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) { + 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) { + setProtocol(nProtocol); + this->setPulseLength(nPulseLength); +} + + +/** + * Sets pulse length in microseconds + */ +void RCSwitch::setPulseLength(int nPulseLength) { + this->protocol.pulseLength = nPulseLength; +} + +/** + * Sets Repeat Transmits + */ +void RCSwitch::setRepeatTransmit(int nRepeatTransmit) { + this->nRepeatTransmit = nRepeatTransmit; +} + +/** + * Set Receiving Tolerance + */ +#if not defined( RCSwitchDisableReceiving ) +void RCSwitch::setReceiveTolerance(int nPercent) { + RCSwitch::nReceiveTolerance = nPercent; +} +#endif + + +/** + * Enable transmissions + * + * @param nTransmitterPin Arduino Pin to which the sender is connected to + */ +void RCSwitch::enableTransmit(int nTransmitterPin) { + this->nTransmitterPin = nTransmitterPin; + pinMode(this->nTransmitterPin, OUTPUT); +} + +/** + * Disable transmissions + */ +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) ); +} + +/** + * Get code value to turn 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) + */ +unsigned long RCSwitch::switchOnCode(char sGroup, int nDevice) { + return this->getBitPattern( this->getCodeWordD(sGroup, nDevice, true) ); +} + +/** + * Get code value to turn 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) + */ +unsigned long RCSwitch::switchOffCode(char sGroup, int nDevice) { + return this->getBitPattern( this->getCodeWordD(sGroup, nDevice, false) ); +} + +/** + * Switch a remote switch on (Type C Intertechno) + * + * @param sFamily Familycode (a..f) + * @param nGroup Number of group (1..4) + * @param nDevice Number of device (1..4) + */ +void RCSwitch::switchOn(char sFamily, int nGroup, int nDevice) { + this->sendTriState( this->getCodeWordC(sFamily, nGroup, nDevice, true) ); +} + +/** + * Switch a remote switch off (Type C Intertechno) + * + * @param sFamily Familycode (a..f) + * @param nGroup Number of group (1..4) + * @param nDevice Number of device (1..4) + */ +void RCSwitch::switchOff(char sFamily, int nGroup, int nDevice) { + this->sendTriState( this->getCodeWordC(sFamily, nGroup, nDevice, false) ); +} + +/** + * Get code value to turn a remote switch on (Type C Intertechno) + * + * @param sFamily Familycode (a..f) + * @param nGroup Number of group (1..4) + * @param nDevice Number of device (1..4) + */ +unsigned long RCSwitch::switchOnCode(char sFamily, int nGroup, int nDevice) { + return this->getBitPattern( this->getCodeWordC(sFamily, nGroup, nDevice, true) ); +} + +/** + * Get code value to turn a remote switch off (Type C Intertechno) + * + * @param sFamily Familycode (a..f) + * @param nGroup Number of group (1..4) + * @param nDevice Number of device (1..4) + */ +unsigned long RCSwitch::switchOffCode(char sFamily, int nGroup, int nDevice) { + return this->getBitPattern( this->getCodeWordC(sFamily, nGroup, nDevice, false) ); +} + +/** + * Switch a remote switch on (Type B with two rotary/sliding switches) + * + * @param nAddressCode Number of the switch group (1..4) + * @param nChannelCode Number of the switch itself (1..4) + */ +void RCSwitch::switchOn(int nAddressCode, int nChannelCode) { + this->sendTriState( this->getCodeWordB(nAddressCode, nChannelCode, true) ); +} + +/** + * Switch a remote switch off (Type B with two rotary/sliding switches) + * + * @param nAddressCode Number of the switch group (1..4) + * @param nChannelCode Number of the switch itself (1..4) + */ +void RCSwitch::switchOff(int nAddressCode, int nChannelCode) { + this->sendTriState( this->getCodeWordB(nAddressCode, nChannelCode, false) ); +} + +/** + * Get code value to turn a remote switch on (Type B with two rotary/sliding switches) + * + * @param nAddressCode Number of the switch group (1..4) + * @param nChannelCode Number of the switch itself (1..4) + */ +unsigned long RCSwitch::switchOnCode(int nAddressCode, int nChannelCode) { + this->getBitPattern( this->getCodeWordB(nAddressCode, nChannelCode, true) ); +} + +/** + * Get code value to turn a remote switch off (Type B with two rotary/sliding switches) + * + * @param nAddressCode Number of the switch group (1..4) + * @param nChannelCode Number of the switch itself (1..4) + */ +unsigned long RCSwitch::switchOffCode(int nAddressCode, int nChannelCode) { + this->getBitPattern( this->getCodeWordB(nAddressCode, nChannelCode, false) ); +} + +/** + * 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..5) + */ +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..5) + */ +void RCSwitch::switchOff(const char* sGroup, int nChannel) { + const char* code[6] = { "00000", "10000", "01000", "00100", "00010", "00001" }; + this->switchOff(sGroup, code[nChannel]); +} + +/** + * 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 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) + * + * @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::switchOff(const char* sGroup, const char* sDevice) { + this->sendTriState( this->getCodeWordA(sGroup, sDevice, false) ); +} + +/** + * Get code to turn 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 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") + */ +unsigned long RCSwitch::switchOnCode(const char* sGroup, const char* sDevice) { + this->getBitPattern( this->getCodeWordA(sGroup, sDevice, true) ); +} + +/** + * Get code to turn 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 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") + */ +unsigned long RCSwitch::switchOffCode(const char* sGroup, const char* sDevice) { + this->getBitPattern( this->getCodeWordA(sGroup, sDevice, false) ); +} + +/** + * Returns a char[13], representing the code word to be send. + * + */ +char* RCSwitch::getCodeWordA(const char* sGroup, const char* sDevice, bool bStatus) { + static char sReturn[13]; + int nReturnPos = 0; + + for (int i = 0; i < 5; i++) { + sReturn[nReturnPos++] = (sGroup[i] == '0') ? 'F' : '0'; + } + + for (int i = 0; i < 5; i++) { + sReturn[nReturnPos++] = (sDevice[i] == '0') ? 'F' : '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 = 1; i <= 4; i++) { + sReturn[nReturnPos++] = (nAddressCode == i) ? '0' : 'F'; + } + + for (int i = 1; i <= 4; i++) { + sReturn[nReturnPos++] = (nChannelCode == i) ? '0' : 'F'; + } + + 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, bool bStatus) { + static char sReturn[13]; + int nReturnPos = 0; + + int nFamily = (int)sFamily - 'a'; + if ( nFamily < 0 || nFamily > 15 || nGroup < 1 || nGroup > 4 || nDevice < 1 || nDevice > 4) { + return 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'; + sReturn[nReturnPos++] = bStatus ? 'F' : '0'; + + sReturn[nReturnPos] = '\0'; + return sReturn; +} + +/** + * 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 + */ +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; + } + + for (int i = 0; i < 4; i++) { + sReturn[nReturnPos++] = (nGroup == i) ? '1' : 'F'; + } + + for (int i = 1; i <= 3; i++) { + sReturn[nReturnPos++] = (nDevice == i) ? '1' : 'F'; + } + + sReturn[nReturnPos++] = '0'; + sReturn[nReturnPos++] = '0'; + sReturn[nReturnPos++] = '0'; + + sReturn[nReturnPos++] = bStatus ? '1' : '0'; + sReturn[nReturnPos++] = bStatus ? '0' : '1'; + + sReturn[nReturnPos] = '\0'; + return sReturn; +} + +/** + * @param sCodeWord a tristate code word consisting of the letter 0, 1, F + */ +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); +} + +/** + * @param sCodeWord a tristate code word consisting of the letter 0, 1, F + */ +unsigned long RCSwitch::getBitPattern(const char* sCodeWord) { + // turn the tristate code word into the corresponding bit pattern, then return 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; + } + return code; +} + +/** + * @param sCodeWord a binary code word consisting of the letter 0, 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); +} + +/** + * 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::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 +} + +/** + * Transmit a single high-low pulse. + */ +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 + */ +void RCSwitch::enableReceive(int interrupt) { + this->nReceiverInterrupt = interrupt; + this->enableReceive(); +} + +void RCSwitch::enableReceive() { + if (this->nReceiverInterrupt != -1) { + 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 + } +} + +/** + * 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 != 0; +} + +void RCSwitch::resetAvailable() { + RCSwitch::nReceivedValue = 0; +} + +unsigned long RCSwitch::getReceivedValue() { + return RCSwitch::nReceivedValue; +} + +unsigned int RCSwitch::getReceivedBitlength() { + return RCSwitch::nReceivedBitlength; +} + +unsigned int RCSwitch::getReceivedDelay() { + return RCSwitch::nReceivedDelay; +} + +unsigned int RCSwitch::getReceivedProtocol() { + return RCSwitch::nReceivedProtocol; +} + +unsigned int* RCSwitch::getReceivedRawdata() { + return RCSwitch::timings; +} + +/* helper function for the receiveProtocol method */ +static inline unsigned int diff(int A, int B) { + return abs(A - B); +} + +/** + * + */ +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; + + /* 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 + return false; + } + } + + 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; +} + +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; + } + + // detect overflow + if (changeCount >= RCSWITCH_MAX_CHANGES) { + changeCount = 0; + repeatCount = 0; + } + + RCSwitch::timings[changeCount++] = duration; + lastTime = time; +} +#endif diff --git a/Firmware/Source/v1.2/grove_pi_v1_2_7/RCSwitch.h b/Firmware/Source/v1.2/grove_pi_v1_2_7/RCSwitch.h new file mode 100644 index 00000000..36ee8b13 --- /dev/null +++ b/Firmware/Source/v1.2/grove_pi_v1_2_7/RCSwitch.h @@ -0,0 +1,163 @@ +/* + RCSwitch - Arduino libary for remote control outlet switches + 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: 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 + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef _RCSwitch_h +#define _RCSwitch_h + + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "Arduino.h" +#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 +#else + #include "WProgram.h" +#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: + RCSwitch(); + + void switchOn(int nGroupNumber, int nSwitchNumber); + void switchOff(int nGroupNumber, 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 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); + unsigned long switchOnCode(int nGroupNumber, int nSwitchNumber); + unsigned long switchOffCode(int nGroupNumber, int nSwitchNumber); + unsigned long switchOnCode(char sFamily, int nGroup, int nDevice); + unsigned long switchOffCode(char sFamily, int nGroup, int nDevice); + unsigned long switchOnCode(const char* sGroup, const char* sDevice); + unsigned long switchOffCode(const char* sGroup, const char* sDevice); + unsigned long switchOnCode(char sGroup, int nDevice); + unsigned long switchOffCode(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(); + + unsigned long getReceivedValue(); + unsigned int getReceivedBitlength(); + unsigned int getReceivedDelay(); + 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); + #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* 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); + unsigned long getBitPattern(const char *sCodeWord); + void transmit(HighLow pulses); + + #if not defined( RCSwitchDisableReceiving ) + static void handleInterrupt(); + static bool receiveProtocol(const int p, unsigned int changeCount); + int nReceiverInterrupt; + #endif + int nTransmitterPin; + int nRepeatTransmit; + + Protocol protocol; + + #if not defined( RCSwitchDisableReceiving ) + static int nReceiveTolerance; + static unsigned long nReceivedValue; + static unsigned int nReceivedBitlength; + 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 + +}; + +#endif diff --git a/Firmware/Source/v1.2/grove_pi_v1_2_7/README.md b/Firmware/Source/v1.2/grove_pi_v1_2_7/README.md index 9b125c5e..4362e10e 100644 --- a/Firmware/Source/v1.2/grove_pi_v1_2_7/README.md +++ b/Firmware/Source/v1.2/grove_pi_v1_2_7/README.md @@ -82,7 +82,6 @@ I2C address: 0x4 | Chainable RGB set LEDs with modulo | 94 | pin | offset | divisor | _none_ | Set color on all LEDs >= offset when mod remainder is 0 | | Chainable RGB set level | 95 | pin | level | reverse | _none_ | Set color on all LEDs <= level, outwards unless reverse | - ### Library Dependencies * [DHT](https://github.com/karan259/DHT-sensor-library) @@ -91,3 +90,4 @@ I2C address: 0x4 * [Grove_LED_bar](https://github.com/Seeed-Studio/Grove_LED_Bar) * [TM1637](https://github.com/mcauser/TM1637-led-driver-7-segment) * [Chainable_RGB_LED](https://github.com/mcauser/Grove-Chainable-RGB-LED) +* [Modified version of RCSwitch](https://github.com/sui77/rc-switch) diff --git a/Firmware/Source/v1.2/grove_pi_v1_2_7/grove_pi_v1_2_7.ino b/Firmware/Source/v1.2/grove_pi_v1_2_7/grove_pi_v1_2_7.ino index c39ccea6..eb11b17d 100644 --- a/Firmware/Source/v1.2/grove_pi_v1_2_7/grove_pi_v1_2_7.ino +++ b/Firmware/Source/v1.2/grove_pi_v1_2_7/grove_pi_v1_2_7.ino @@ -6,6 +6,10 @@ #include "Encoder.h" #include "TimerOne.h" +// Note that RCSwitch.h is a patched version of the one from the https://github.com/sui77/rc-switch repository. +// Additional functions have been added to get a numerical version of radio sequences sent/received. +#include "RCSwitch.h" + DHT dht; Grove_LED_Bar ledbar[6]; // 7 instances for D2-D8, however, max 4 bars, you can't use adjacent sockets, 4 pin display TM1637 fourdigit[6]; // 7 instances for D2-D8, however, max 4 displays, you can't use adjacent sockets, 4 pin display @@ -25,6 +29,15 @@ ChainableLED rgbled[6]; // 7 instances for D2-D8 #define flow_en_cmd 18 #define flow_dis_cmd 13 +#define rc_switch_send_cmd 110 +#define rc_switch_subscribe_cmd 111 +#define rc_switch_read_cmd 112 +#define rc_switch_typeA 0 +#define rc_switch_typeB 1 +#define rc_switch_typeC 2 +#define rc_switch_typeD 3 +#define rc_switch_max_sub 8 // Max number of switch subscriptions + int cmd[5]; int index=0; int flag=0; @@ -60,6 +73,23 @@ int flow_run_bk=0; long flow_read_start; byte flow_val[3]; //Given it's own I2C buffer so that it does not corrupt the data from other sensors when running in background +// 433MHz RCSwitch variables +RCSwitch rc_switch; // Library instance +// TODO define constants for on/off +uint8_t rc_switch_state; // Desired state of the remote-controlled switch. 0=OFF, 1=ON +uint8_t rc_switch_type; // Remote-controlled switch type. 0 == type A, 1 == type B, 2 == type C, 3 == type D. +char rc_switch_typeA_group [6]; // For type A switches, group DIP setting as a binary string ("00110" == OFF OFF ON ON OFF) +char rc_switch_typeA_device [6]; // For type A switches, device DIP setting as a binary string ("00110" == OFF OFF ON ON OFF) +struct rc_switch_subscription_struct { // Struct describing a subscription to a set of on/off codes + unsigned long onCode = 0; + unsigned long offCode = 0; + byte lastStatus = 255; // 255: unknown +}; +struct rc_switch_subscription_struct rc_switch_subscription[rc_switch_max_sub - 1]; // Subscription storage +uint8_t rc_switch_subscription_number; +boolean rc_switch_run_bk = false; +unsigned long rc_switch_code; + void setup() { // Serial.begin(38400); // start serial for output @@ -67,7 +97,6 @@ void setup() Wire.onReceive(receiveData); Wire.onRequest(sendData); - attachInterrupt(0,readPulseDust,CHANGE); } int pin; int j; @@ -536,6 +565,511 @@ void loop() detachInterrupt(0); cmd[0]=0; } + /* Command 110 - 433MHz transmitter: send command to radio-controlled switch or socket. + * + * Based on the RCSwitch library (https://github.com/sui77/rc-switch) + * This library supports 4 types of switches, with different parameters. + * For each of these, the firmware expects different parameters, as follows: + * + * +-------------------------------------------------------------------------------+ + * | Type A switch | + * +---------+---------+-----------------------------------------------------------+ + * | Byte | Bits | Description | + * +---------+---------+-----------------------------------------------------------+ + * | 1 | 0 - 7 | Command byte: send command to RC switch. | + * | | | Decimal value: 110 | + * +---------+---------+-----------------------------------------------------------+ + * | 2 | 0 | Pin on which the 433 MHz transmitter module is connected. | + * | | | 0 -> D2 / 1 -> D3 | + * | +---------+-----------------------------------------------------------+ + * | | 1 - 4 | Unused | + * | | | | + * | +---------+-----------------------------------------------------------+ + * | | 5 | Requested switch state. | + * | | | 0 = off / 1 = on | + * | +---------+-----------------------------------------------------------+ + * | | 6 - 7 | RC switch type. | + * | | | Type A: 00 | + * +---------+---------+-----------------------------------------------------------+ + * | 3 | 0 - 4 | Group DIP switch. | + * | | | Ex: OFF-ON-ON-ON-OFF --> 01110 | + * | +---------+-----------------------------------------------------------+ + * | | 5 - 7 | Unused | + * | | | | + * +---------+---------+-----------------------------------------------------------+ + * | 4 | 0 - 4 | Device DIP switch. | + * | | | Ex: OFF-ON-ON-ON-OFF --> 01110 | + * | +---------+-----------------------------------------------------------+ + * | | 5 - 7 | Unused | + * | | | | + * +---------+---------+-----------------------------------------------------------+ + * + * + * +-------------------------------------------------------------------------------+ + * | Type B switch | + * +---------+---------+-----------------------------------------------------------+ + * | Byte | Bits | Description | + * +---------+---------+-----------------------------------------------------------+ + * | 1 | 0 - 7 | Command byte: send command to RC switch. | + * | | | Decimal value: 110 | + * +---------+---------+-----------------------------------------------------------+ + * | 2 | 0 | Pin on which the 433 MHz transmitter module is connected. | + * | | | 0 -> D2 / 1 -> D3 | + * | +---------+-----------------------------------------------------------+ + * | | 1 - 4 | Unused | + * | | | | + * | +---------+-----------------------------------------------------------+ + * | | 5 | Requested switch state. | + * | | | 0 = off / 1 = on | + * | +---------+-----------------------------------------------------------+ + * | | 6 - 7 | RC switch type. | + * | | | Type B: 01 | + * +---------+---------+-----------------------------------------------------------+ + * | 3 | 0 - 7 | Group id (1 - 4). | + * | | | Ex: 3 --> 0000 0011 | + * +---------+---------+-----------------------------------------------------------+ + * | 4 | 0 - 7 | Device id (1 - 4). | + * | | | Ex: 3 --> 0000 0011 | + * +---------+---------+-----------------------------------------------------------+ + * + * + * +-------------------------------------------------------------------------------+ + * | Type C switch | + * +---------+---------+-----------------------------------------------------------+ + * | Byte | Bits | Description | + * +---------+---------+-----------------------------------------------------------+ + * | 1 | 0 - 7 | Command byte: send command to RC switch. | + * | | | Decimal value: 110 | + * +---------+---------+-----------------------------------------------------------+ + * | 2 | 0 | Pin on which the 433 MHz transmitter module is connected. | + * | | | 0 -> D2 / 1 -> D3 | + * | +---------+-----------------------------------------------------------+ + * | | 1 - 4 | Unused | + * | | | | + * | +---------+-----------------------------------------------------------+ + * | | 5 | Requested switch state. | + * | | | 0 = off / 1 = on | + * | +---------+-----------------------------------------------------------+ + * | | 6 - 7 | RC switch type. | + * | | | Type C: 10 | + * +---------+---------+-----------------------------------------------------------+ + * | 3 | 0 - 7 | Device family ('a' - 'f'), as the ASCII code of the | + * | | | desired letter. | + * | | | Ex: 'b' --> 0110 0001 | + * +---------+---------+-----------------------------------------------------------+ + * | 4 | 0 - 1 | Device id (1 - 4), minus 1. | + * | | | Ex: device #3 --> 10 | + * | +---------+-----------------------------------------------------------+ + * | | 2 - 3 | Device group (1 - 4), minus 1. | + * | | | Ex: group #1 --> 00 | + * | +---------+-----------------------------------------------------------+ + * | | 4 - 7 | Unused | + * | | | | + * +---------+---------+-----------------------------------------------------------+ + * + * + * +-------------------------------------------------------------------------------+ + * | Type D switch | + * +---------+---------+-----------------------------------------------------------+ + * | Byte | Bits | Description | + * +---------+---------+-----------------------------------------------------------+ + * | 1 | 0 - 7 | Command byte: send command to RC switch. | + * | | | Decimal value: 110 | + * +---------+---------+-----------------------------------------------------------+ + * | 2 | 0 | Pin on which the 433 MHz transmitter module is connected. | + * | | | 0 -> D2 / 1 -> D3 | + * | +---------+-----------------------------------------------------------+ + * | | 1 - 4 | Unused | + * | | | | + * | +---------+-----------------------------------------------------------+ + * | | 5 | Requested switch state. | + * | | | 0 = off / 1 = on | + * | +---------+-----------------------------------------------------------+ + * | | 6 - 7 | RC switch type. | + * | | | Type D: 11 | + * +---------+---------+-----------------------------------------------------------+ + * | 3 | 0 - 7 | Device family ('A' - 'D'), as the ASCII code of the | + * | | | desired letter. | + * | | | Ex: 'C' --> 0100 0011 | + * +---------+---------+-----------------------------------------------------------+ + * | 4 | 0 - 7 | Device id (1 - 3). | + * | | | Ex: 3 --> 0000 0011 | + * +---------+---------+-----------------------------------------------------------+ + */ + else if (cmd[0]==rc_switch_send_cmd) { + if (run_once == 1) {// Ensure we do not repeatedly execute this command while waiting for another one + + // Apply bit-mask to recover data from the 2nd command byte + // pin: bit 0 of 2nd byte. 0 --> pin 2. 1 --> pin 3. + pin = 2 + (cmd[1] & 1); + // state (on/off) : bit 5 + rc_switch_state = uint8_t(cmd[1] >> 5 & 1); + // remote switch type: 4 possible types encoded in bits 6-7 + rc_switch_type = uint8_t(cmd[1] >> 6); + + // Enable radio + rc_switch.enableTransmit(pin); + + // Handle remaining bytes depending on the switch type + switch (rc_switch_type) { + case rc_switch_typeA: { + // For this type of remote controlled switches, the RCSwitch library requires 2 parameters, group and + // device, as 5-chars binary strings, padded with zeroes. These parameters should match DIP switches present + // on the switch device. + // Ex: a DIP switch set as OFF-ON-ON-ON-OFF is represented by the parameter string "01110" + + // Conversion of the group parameter in the appropriate format + for (i = 4; i >= 0; --i) { + rc_switch_typeA_group[4 - i] = (cmd[2] & (1 << i) ? '1' : '0'); + } + rc_switch_typeA_group[5] = '\0'; + + // Conversion of the device parameter in the appropriate format + for (i = 4; i >= 0; --i) { + rc_switch_typeA_device[4 - i] = (cmd[3] & (1 << i) ? '1' : '0'); + } + rc_switch_typeA_device[5] = '\0'; + + + if (rc_switch_state == 0) { + rc_switch.switchOff(rc_switch_typeA_group, rc_switch_typeA_device); + rc_switch_code = rc_switch.switchOffCode(rc_switch_typeA_group, rc_switch_typeA_device); + } else { + rc_switch.switchOn(rc_switch_typeA_group, rc_switch_typeA_device); + rc_switch_code = rc_switch.switchOnCode(rc_switch_typeA_group, rc_switch_typeA_device); + } + break; + } + case rc_switch_typeB: { + // For this type of remote controlled switches, the RCSwitch library requires two parameters: + // group as an int [1..4] --> directly passed in 3rd byte cmd[2] + // device as an int [1..4] --> directly passed in 4th byte cmd[3] + if (rc_switch_state == 0) { + rc_switch.switchOff(cmd[2], cmd[3]); + rc_switch_code = rc_switch.switchOffCode(cmd[2], cmd[3]); + } else { + rc_switch.switchOn(cmd[2], cmd[3]); + rc_switch_code = rc_switch.switchOnCode(cmd[2], cmd[3]); + } + break; + } + case rc_switch_typeC: { + // For this type of remote controlled switches, the RCSwitch library requires three parameters: + // family code as a char ['a'..'f'] --> directly passed in 3rd byte cmd[2] + // group as int [1..4] --> this value minus one is passed using bits 2 and 3 of the 4th byte cmd[3] + // device as int [1..4] --> this value minus one is passed using bits 0 and 1 of the 4th byte cmd[3] + if (rc_switch_state == 0) { + rc_switch.switchOff(char(cmd[2]), 1 + (cmd[3] >> 2 & 3), 1 + (cmd[3] & 3)); + rc_switch_code = rc_switch.switchOffCode(char(cmd[2]), 1 + (cmd[3] >> 2 & 3), 1 + (cmd[3] & 3)); + } else { + rc_switch.switchOn(char(cmd[2]), 1 + (cmd[3] >> 2 & 3), 1 + (cmd[3] & 3)); + rc_switch_code = rc_switch.switchOnCode(char(cmd[2]), 1 + (cmd[3] >> 2 & 3), 1 + (cmd[3] & 3)); + } + break; + } + case rc_switch_typeD: { + // For this type of remote controlled switches, the RCSwitch library requires two parameters: + // group as a char ['A'..'D'] --> directly passed in 3rd byte cmd[2] + // device as an int [1..3] --> directly passed in 4th byte cmd[3] + if (rc_switch_state == 0) { + rc_switch.switchOff(char(cmd[2]), cmd[3]); + rc_switch_code = rc_switch.switchOffCode(char(cmd[2]), cmd[3]); + } else { + rc_switch.switchOn(char(cmd[2]), cmd[3]); + rc_switch_code = rc_switch.switchOffCode(char(cmd[2]), cmd[3]); + } + break; + } + default: { + // Unknown switch type --> do nothing + } + } + // Disable radio + rc_switch.disableTransmit(); + } + cmd[0] = 0; + run_once = 0; + } + /* Command 111 - 433MHz receiver: subscribe to radio commands + * + * Based on the RCSwitch library (https://github.com/sui77/rc-switch) + * This library supports 4 types of switches, with different parameters. + * For each of these, the firmware expects different parameters, as follows: + * + * +-------------------------------------------------------------------------------+ + * | Type A switch | + * +---------+---------+-----------------------------------------------------------+ + * | Byte | Bits | Description | + * +---------+---------+-----------------------------------------------------------+ + * | 1 | 0 - 7 | Command byte: subscribe to RC switch commands. | + * | | | Decimal value: 111 | + * +---------+---------+-----------------------------------------------------------+ + * | 2 | 0 | Pin on which the 433 MHz receiver module is connected. | + * | | | 0 -> D2 / 1 -> D3 | + * | +---------+-----------------------------------------------------------+ + * | | 1 - 3 | Subscription number | + * | | | | + * | +---------+-----------------------------------------------------------+ + * | | 4 | Unused | + * | | | | + * | +---------+-----------------------------------------------------------+ + * | | 5 | Initial state. | + * | | | 0 = off / 1 = on | + * | +---------+-----------------------------------------------------------+ + * | | 6 - 7 | RC switch type. | + * | | | Type A: 00 | + * +---------+---------+-----------------------------------------------------------+ + * | 3 | 0 - 4 | Group DIP switch. | + * | | | Ex: OFF-ON-ON-ON-OFF --> 01110 | + * | +---------+-----------------------------------------------------------+ + * | | 5 - 7 | Unused | + * | | | | + * +---------+---------+-----------------------------------------------------------+ + * | 4 | 0 - 4 | Device DIP switch. | + * | | | Ex: OFF-ON-ON-ON-OFF --> 01110 | + * | +---------+-----------------------------------------------------------+ + * | | 5 - 7 | Unused | + * | | | | + * +---------+---------+-----------------------------------------------------------+ + * + * + * +-------------------------------------------------------------------------------+ + * | Type B switch | + * +---------+---------+-----------------------------------------------------------+ + * | Byte | Bits | Description | + * +---------+---------+-----------------------------------------------------------+ + * | 1 | 0 - 7 | Command byte: subscribe to RC switch commands. | + * | | | Decimal value: 111 | + * +---------+---------+-----------------------------------------------------------+ + * | 2 | 0 | Pin on which the 433 MHz receiver module is connected. | + * | | | 0 -> D2 / 1 -> D3 | + * | +---------+-----------------------------------------------------------+ + * | | 1 - 3 | Subscription number | + * | | | | + * | +---------+-----------------------------------------------------------+ + * | | 4 | Unused | + * | | | | + * | +---------+-----------------------------------------------------------+ + * | | 5 | Initial state. | + * | | | 0 = off / 1 = on | + * | +---------+-----------------------------------------------------------+ + * | | 6 - 7 | RC switch type. | + * | | | Type B: 01 | + * +---------+---------+-----------------------------------------------------------+ + * | 3 | 0 - 7 | Group id (1 - 4). | + * | | | Ex: 3 --> 0000 0011 | + * +---------+---------+-----------------------------------------------------------+ + * | 4 | 0 - 7 | Device id (1 - 4). | + * | | | Ex: 3 --> 0000 0011 | + * +---------+---------+-----------------------------------------------------------+ + * + * + * +-------------------------------------------------------------------------------+ + * | Type C switch | + * +---------+---------+-----------------------------------------------------------+ + * | Byte | Bits | Description | + * +---------+---------+-----------------------------------------------------------+ + * | 1 | 0 - 7 | Command byte: subscribe to RC switch commands. | + * | | | Decimal value: 111 | + * +---------+---------+-----------------------------------------------------------+ + * | 2 | 0 | Pin on which the 433 MHz receiver module is connected. | + * | | | 0 -> D2 / 1 -> D3 | + * | +---------+-----------------------------------------------------------+ + * | | 1 - 3 | Subscription number | + * | | | | + * | +---------+-----------------------------------------------------------+ + * | | 4 | Unused | + * | | | | + * | +---------+-----------------------------------------------------------+ + * | | 5 | Initial state. | + * | | | 0 = off / 1 = on | + * | +---------+-----------------------------------------------------------+ + * | | 6 - 7 | RC switch type. | + * | | | Type C: 10 | + * +---------+---------+-----------------------------------------------------------+ + * | 3 | 0 - 7 | Device family ('a' - 'f'), as the ASCII code of the | + * | | | desired letter. | + * | | | Ex: 'b' --> 0110 0001 | + * +---------+---------+-----------------------------------------------------------+ + * | 4 | 0 - 1 | Device id (1 - 4), minus 1. | + * | | | Ex: device #3 --> 10 | + * | +---------+-----------------------------------------------------------+ + * | | 2 - 3 | Device group (1 - 4), minus 1. | + * | | | Ex: group #1 --> 00 | + * | +---------+-----------------------------------------------------------+ + * | | 4 - 7 | Unused | + * | | | | + * +---------+---------+-----------------------------------------------------------+ + * + * + * +-------------------------------------------------------------------------------+ + * | Type D switch | + * +---------+---------+-----------------------------------------------------------+ + * | Byte | Bits | Description | + * +---------+---------+-----------------------------------------------------------+ + * | 1 | 0 - 7 | Command byte: subscribe to RC switch commands. | + * | | | Decimal value: 111 | + * +---------+---------+-----------------------------------------------------------+ + * | 2 | 0 | Pin on which the 433 MHz receiver module is connected. | + * | | | 0 -> D2 / 1 -> D3 | + * | +---------+-----------------------------------------------------------+ + * | | 1 - 3 | Subscription number | + * | | | | + * | +---------+-----------------------------------------------------------+ + * | | 4 | Unused | + * | | | | + * | +---------+-----------------------------------------------------------+ + * | | 5 | Initial state. | + * | | | 0 = off / 1 = on | + * | +---------+-----------------------------------------------------------+ + * | | 6 - 7 | RC switch type. | + * | | | Type D: 11 | + * +---------+---------+-----------------------------------------------------------+ + * | 3 | 0 - 7 | Device family ('A' - 'D'), as the ASCII code of the | + * | | | desired letter. | + * | | | Ex: 'C' --> 0100 0011 | + * +---------+---------+-----------------------------------------------------------+ + * | 4 | 0 - 7 | Device id (1 - 3). | + * | | | Ex: 3 --> 0000 0011 | + * +---------+---------+-----------------------------------------------------------+ + */ + else if (cmd[0]==rc_switch_subscribe_cmd) { + if (run_once == 1) {// Ensure we do not repeatedly execute this command while waiting for another one + + // Apply bit-mask to recover data from the 2nd command byte + // pin: bit 0. 0 --> pin 2 / 1 --> pin 3 + pin = 2 + (cmd[1] & 1); + + // subscription number: bits 1-3 + rc_switch_subscription_number = uint8_t(cmd[1] >> 1 & 7); + + // initial state (on/off): bit 5 + rc_switch_state = uint8_t(cmd[1] >> 5 & 1); + + // remote switch type: 4 possible types encoded in bits 6-7 + rc_switch_type = uint8_t(cmd[1] >> 6); + + // Enable radio if it is not already done + if (!rc_switch_run_bk) { + rc_switch.enableReceive(digitalPinToInterrupt(pin)); + rc_switch_run_bk = true; + } + + // Handle remaining bytes depending on the switch type + switch (rc_switch_type) { + case rc_switch_typeA: { + // For this type of remote controlled switches, the RCSwitch library requires 2 parameters, group and + // device, as 5-chars binary strings, padded with zeroes. These parameters should match DIP switches present + // on the switch device. + // Ex: a DIP switch set as OFF-ON-ON-ON-OFF is represented by the parameter string "01110" + + // Conversion of the group parameter in the appropriate format + for (i = 4; i >= 0; --i) { + rc_switch_typeA_group[4 - i] = (cmd[2] & (1 << i) ? '1' : '0'); + } + rc_switch_typeA_group[5] = '\0'; + + // Conversion of the device parameter in the appropriate format + for (i = 4; i >= 0; --i) { + rc_switch_typeA_device[4 - i] = (cmd[3] & (1 << i) ? '1' : '0'); + } + rc_switch_typeA_device[5] = '\0'; + + // Store subscription + rc_switch_subscription[rc_switch_subscription_number].offCode = + rc_switch.switchOffCode(rc_switch_typeA_group, rc_switch_typeA_device); + rc_switch_subscription[rc_switch_subscription_number].onCode = + rc_switch.switchOnCode(rc_switch_typeA_group, rc_switch_typeA_device); + rc_switch_subscription[rc_switch_subscription_number].lastStatus = rc_switch_state; + + break; + } + case rc_switch_typeB: { + // For this type of remote controlled switches, the RCSwitch library requires two parameters: + // group as an int [1..4] --> directly passed in 3rd byte cmd[2] + // device as an int [1..4] --> directly passed in 4th byte cmd[3] + + // Store subscription + rc_switch_subscription[rc_switch_subscription_number].offCode = + rc_switch.switchOffCode(cmd[2], cmd[3]); + rc_switch_subscription[rc_switch_subscription_number].onCode = + rc_switch.switchOnCode(cmd[2], cmd[3]); + + rc_switch_subscription[rc_switch_subscription_number].lastStatus = rc_switch_state; + break; + } + case rc_switch_typeC: { + // For this type of remote controlled switches, the RCSwitch library requires three parameters: + // family code as a char ['a'..'f'] --> directly passed in 3rd byte cmd[2] + // group as int [1..4] --> this value minus one is passed using bits 2 and 3 of the 4th byte cmd[3] + // device as int [1..4] --> this value minus one is passed using bits 0 and 1 of the 4th byte cmd[3] + + // Store subscription + rc_switch_subscription[rc_switch_subscription_number].offCode = + rc_switch.switchOffCode(char(cmd[2]), 1 + (cmd[3] >> 2 & 3), 1 + (cmd[3] & 3)); + rc_switch_subscription[rc_switch_subscription_number].onCode = + rc_switch.switchOnCode(char(cmd[2]), 1 + (cmd[3] >> 2 & 3), 1 + (cmd[3] & 3)); + rc_switch_subscription[rc_switch_subscription_number].lastStatus = rc_switch_state; + break; + } + case rc_switch_typeD: { + // For this type of remote controlled switches, the RCSwitch library requires two parameters: + // group as a char ['A'..'D'] --> directly passed in 3rd byte cmd[2] + // device as an int [1..3] --> directly passed in 4th byte cmd[3] + + // Store subscription + rc_switch_subscription[rc_switch_subscription_number].offCode = + rc_switch.switchOffCode(char(cmd[2]), cmd[3]); + rc_switch_subscription[rc_switch_subscription_number].onCode = + rc_switch.switchOnCode(char(cmd[2]), cmd[3]); + rc_switch_subscription[rc_switch_subscription_number].lastStatus = rc_switch_state; + break; + } + default: { + // Unknown switch type --> do nothing + } + + } + } + cmd[0] = 0; + run_once = 0; + } + /* Command 112 - 433MHz receiver: read last status + * + * Retrieve last received command for a subscription slot + * + * +---------+---------+-----------------------------------------------------------+ + * | Byte | Bits | Description | + * +---------+---------+-----------------------------------------------------------+ + * | 1 | 0 - 7 | Command byte: subscribe to RC switch commands. | + * | | | Decimal value: 112 | + * +---------+---------+-----------------------------------------------------------+ + * | 2 | 0 | Unused | + * | | | | + * | +---------+-----------------------------------------------------------+ + * | | 1 - 3 | Subscription number | + * | | | | + * | +---------+-----------------------------------------------------------+ + * | | 4 - 7 | Unused | + * | | | | + * +---------+---------+-----------------------------------------------------------+ + * | 3 | 0 - 7 | Unused | + * | | | | + * +---------+---------+-----------------------------------------------------------+ + * | 4 | 0 - 7 | Unused | + * | | | | + * +---------+---------+-----------------------------------------------------------+ + */ + else if (cmd[0]==rc_switch_read_cmd) { + if (run_once == 1) { // Ensure we do not repeatedly execute this command while waiting for another one + + // Apply bit-mask to recover data from the 2nd command byte + // subscription number: bits 1-3 + rc_switch_subscription_number = uint8_t(cmd[1] >> 1 & 7); + val = rc_switch_subscription[rc_switch_subscription_number].lastStatus; + } + run_once = 0; + } } //Dust sensor can run in background so has a dedicated if condition if(dust_run_bk) @@ -585,6 +1119,23 @@ void loop() flow_read_start=millis(); } } + + if(rc_switch_run_bk) + { + // Check if we have received a radio command + if (rc_switch.available()) + { + // Check if we have to update one of our subscriptions + for (j = 0; j < rc_switch_max_sub; j++) { + if (rc_switch_subscription[j].offCode == rc_switch.getReceivedValue()) { + rc_switch_subscription[j].lastStatus = 0; + } else if (rc_switch_subscription[j].onCode == rc_switch.getReceivedValue()) { + rc_switch_subscription[j].lastStatus = 1; + } + } + rc_switch.resetAvailable(); + } + } } void receiveData(int byteCount) @@ -638,6 +1189,11 @@ void sendData() flow_val[0]=0; cmd[0]=0; } + if(cmd[0]==rc_switch_read_cmd) + { + Wire.write(val); + cmd[0]=0; + } } diff --git a/Software/Python/grove_rflink433mhz_rcswitch/433rf_link_kit.png b/Software/Python/grove_rflink433mhz_rcswitch/433rf_link_kit.png new file mode 100644 index 0000000000000000000000000000000000000000..1fbc35c4b1b47f58b6eb52df83a0a0834de3f7ed GIT binary patch literal 64461 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 Bk#GP2 literal 0 HcmV?d00001 diff --git a/Software/Python/grove_rflink433mhz_rcswitch/README.md b/Software/Python/grove_rflink433mhz_rcswitch/README.md new file mode 100644 index 00000000..f474fb1b --- /dev/null +++ b/Software/Python/grove_rflink433mhz_rcswitch/README.md @@ -0,0 +1,25 @@ +Grove 433MHz Simple RF link kit +=============================== + +![Grove 433MHz Simple RF link kit](433rf_link_kit.png) + +This kit contains two Grove components: a transmitter (TX) and a receiver (RX). +Both are tuned to the 433MHz frequency, which is frequently used in short range communication between devices. +As a result, these components can be used in various use cases. This library helps with some of these. + +GrovePi supported use-cases +=========================== + +Remote switch operation +----------------------- + +Some devices can be remote-controlled using specific codes sent over the 433MHz frequency. This is the case +of some switches or power outlets easily found on the market. +The `grove_433mhz_tx_rcswitch.py` provides support to command such power outlets by embedding the +[RCSwitch library](https://github.com/sui77/rc-switch/wiki). + +Remote control commands reception +--------------------------------- + +These power outlets come with a RF remote control. The `grove_433mhz_rx_rcswitch.py` library allows to listen to +commands sent by such remote controls. \ No newline at end of file diff --git a/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch.py b/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch.py new file mode 100644 index 00000000..b2be7d7e --- /dev/null +++ b/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch.py @@ -0,0 +1,301 @@ +import grovepi + + +class Grove433mhzRxRCSwitch: + """ Receives command codes from remotes sold with radio controlled switches or devices. + + This library receives on / off commands sent from the radio remotes of 4 different types of RC devices. + Each of these types can be identified by the way the device address is configured: + - Type A devices addresses are set by 5 DIP switches + - Type B devices addresses are set by 2 rotary or sliding switches, each with 4 positions + - Type C 'Intertechno' devices addresses are set with 3 elements: family (a to f), group (1 to 4) and device + (1 to 4) + - Type D 'REV' devices addresses are set with 2 elements: group (A to D) and device (1 to 3) + + Using this library requires 2 steps: + 1. Subscribe to the on/off commands normally sent to a device. The GrovePi will from that point listen + to these commands, and store the last one. + + 2. Periodically read the last received command, and act accordingly. + + This library makes use of the RCSwitch library: https://github.com/sui77/rc-switch/ + Please have a look at it for more details on the supported devices. + + """ + + # Remote-controlled switch types + TYPE_A = 0 + TYPE_B = 1 + TYPE_C = 2 + TYPE_D = 3 + + # Last received command + STATE_ON = 1 + STATE_OFF = 0 + STATE_UNKNOWN = 255 + + def __init__(self, rx_pin): + """Sets the Digital pin on which the RX component is wired. + + :param rx_pin: For D2, set to 2. Only pins D2 and D3 are compatible with this grove component. + """ + self.rx_pin = rx_pin + + def subscribe_type_a(self, subscription_number, group, device, initial_state=STATE_UNKNOWN): + """Subscribe to commands normally sent to a type A device. + + Type A devices addresses are typically set by 5 DIP switches. + + :param subscription_number: subscription slot to use. Can be any value from 0 to 7. + + :param group: group DIP switch setting, as a string of five 0s/1s. Set to the '00101' string for a DIP switch + set to OFF OFF ON OFF ON. + :param device: device DIP switch setting, as a string of five 0s/1s. Set to the '00101' string for a DIP switch + set to OFF OFF ON OFF ON. + + :param initial_state: set the value to be returned by the first call to read_subscription. Can be STATE_ON or + STATE_OFF. + """ + + # +-------------------------------------------------------------------------------+ + # | Type A switch | + # +---------+---------+-----------------------------------------------------------+ + # | Byte | Bits | Description | + # +---------+---------+-----------------------------------------------------------+ + # | 1 | 0 - 7 | Command byte: subscribe to RC switch commands. | + # | | | Decimal value: 111 | + # +---------+---------+-----------------------------------------------------------+ + # | 2 | 0 | Pin on which the 433 MHz receiver module is connected. | + # | | | 0 -> D2 / 1 -> D3 | + # | +---------+-----------------------------------------------------------+ + # | | 1 - 3 | Subscription number | + # | | | | + # | +---------+-----------------------------------------------------------+ + # | | 4 | Unused | + # | | | | + # | +---------+-----------------------------------------------------------+ + # | | 5 | Initial state. | + # | | | 0 = off / 1 = on | + # | +---------+-----------------------------------------------------------+ + # | | 6 - 7 | RC switch type. | + # | | | Type A: 00 | + # +---------+---------+-----------------------------------------------------------+ + # | 3 | 0 - 4 | Group DIP switch. | + # | | | Ex: OFF-ON-ON-ON-OFF --> 01110 | + # | +---------+-----------------------------------------------------------+ + # | | 5 - 7 | Unused | + # | | | | + # +---------+---------+-----------------------------------------------------------+ + # | 4 | 0 - 4 | Device DIP switch. | + # | | | Ex: OFF-ON-ON-ON-OFF --> 01110 | + # | +---------+-----------------------------------------------------------+ + # | | 5 - 7 | Unused | + # | | | | + # +---------+---------+-----------------------------------------------------------+ + + grovepi.write_i2c_block(grovepi.address, + grovepi.rcswitch_sub_cmd + + [self.TYPE_A * 64 + initial_state * 32 + subscription_number * 2 + self.rx_pin - 2, + int(group, 2), + int(device, 2)]) + + def subscribe_type_b(self, subscription_number, group, device, initial_state=STATE_UNKNOWN): + """Subscribe to commands normally sent to a type B device. + + Type B devices addresses are typically set by two rotary or sliding switches, numbered from 1 to 4. + + :param subscription_number: subscription slot to use. Can be any value from 0 to 7. + + :param group: group switch setting (1 to 4). + + :param device: device switch setting (1 to 4). + + :param initial_state: set the value to be returned by the first call to read_subscription. Can be STATE_ON or + STATE_OFF. + """ + + # Send command to the firmware, using the following scheme: + # + # +-------------------------------------------------------------------------------+ + # | Type B switch | + # +---------+---------+-----------------------------------------------------------+ + # | Byte | Bits | Description | + # +---------+---------+-----------------------------------------------------------+ + # | 1 | 0 - 7 | Command byte: subscribe to RC switch commands. | + # | | | Decimal value: 111 | + # +---------+---------+-----------------------------------------------------------+ + # | 2 | 0 | Pin on which the 433 MHz receiver module is connected. | + # | | | 0 -> D2 / 1 -> D3 | + # | +---------+-----------------------------------------------------------+ + # | | 1 - 3 | Subscription number | + # | | | | + # | +---------+-----------------------------------------------------------+ + # | | 4 | Unused | + # | | | | + # | +---------+-----------------------------------------------------------+ + # | | 5 | Initial state. | + # | | | 0 = off / 1 = on | + # | +---------+-----------------------------------------------------------+ + # | | 6 - 7 | RC switch type. | + # | | | Type B: 01 | + # +---------+---------+-----------------------------------------------------------+ + # | 3 | 0 - 7 | Group id (1 - 4). | + # | | | Ex: 3 --> 0000 0011 | + # +---------+---------+-----------------------------------------------------------+ + # | 4 | 0 - 7 | Device id (1 - 4). | + # | | | Ex: 3 --> 0000 0011 | + # +---------+---------+-----------------------------------------------------------+ + + grovepi.write_i2c_block(grovepi.address, + grovepi.rcswitch_sub_cmd + + [self.TYPE_B * 64 + initial_state * 32 + subscription_number * 2 + self.rx_pin - 2, + group, + device]) + + def subscribe_type_c(self, subscription_number, family, group, device, initial_state=STATE_UNKNOWN): + """Subscribe to commands normally sent to a type C device. + + :param subscription_number: subscription slot to use. Can be any value from 0 to 7. + + :param family: device family setting ('a' to 'f') + + :param group: device group (1 to 4). + + :param device: device id (1 to 4). + + :param initial_state: set the value to be returned by the first call to read_subscription. Can be STATE_ON or + STATE_OFF. + """ + + # Send command to the firmware, using the following scheme: + # + # +-------------------------------------------------------------------------------+ + # | Type C switch | + # +---------+---------+-----------------------------------------------------------+ + # | Byte | Bits | Description | + # +---------+---------+-----------------------------------------------------------+ + # | 1 | 0 - 7 | Command byte: subscribe to RC switch commands. | + # | | | Decimal value: 111 | + # +---------+---------+-----------------------------------------------------------+ + # | 2 | 0 | Pin on which the 433 MHz receiver module is connected. | + # | | | 0 -> D2 / 1 -> D3 | + # | +---------+-----------------------------------------------------------+ + # | | 1 - 3 | Subscription number | + # | | | | + # | +---------+-----------------------------------------------------------+ + # | | 4 | Unused | + # | | | | + # | +---------+-----------------------------------------------------------+ + # | | 5 | Initial state. | + # | | | 0 = off / 1 = on | + # | +---------+-----------------------------------------------------------+ + # | | 6 - 7 | RC switch type. | + # | | | Type C: 10 | + # +---------+---------+-----------------------------------------------------------+ + # | 3 | 0 - 7 | Device family ('a' - 'f'), as the ASCII code of the | + # | | | desired letter. | + # | | | Ex: 'b' --> 0110 0001 | + # +---------+---------+-----------------------------------------------------------+ + # | 4 | 0 - 1 | Device id (1 - 4), minus 1. | + # | | | Ex: device #3 --> 10 | + # | +---------+-----------------------------------------------------------+ + # | | 2 - 3 | Device group (1 - 4), minus 1. | + # | | | Ex: group #1 --> 00 | + # | +---------+-----------------------------------------------------------+ + # | | 4 - 7 | Unused | + # | | | | + # +---------+---------+-----------------------------------------------------------+ + + grovepi.write_i2c_block(grovepi.address, + grovepi.rcswitch_sub_cmd + + [self.TYPE_C * 64 + initial_state * 32 + subscription_number * 2 + self.rx_pin - 2, + ord(family), + (group - 1) * 4 + (device - 1)]) + + def subscribe_type_d(self, subscription_number, group, device, initial_state=STATE_UNKNOWN): + """Subscribe to commands normally sent to a type D device. + + :param subscription_number: subscription slot to use. Can be any value from 0 to 7. + + :param family: device family setting ('A' to 'D') + + :param device: device id (1 to 4). + + :param initial_state: set the value to be returned by the first call to read_subscription. Can be STATE_ON or + STATE_OFF. + """ + + # Send command to the firmware, using the following scheme: + # + # +-------------------------------------------------------------------------------+ + # | Type D switch | + # +---------+---------+-----------------------------------------------------------+ + # | Byte | Bits | Description | + # +---------+---------+-----------------------------------------------------------+ + # | 1 | 0 - 7 | Command byte: subscribe to RC switch commands. | + # | | | Decimal value: 111 | + # +---------+---------+-----------------------------------------------------------+ + # | 2 | 0 | Pin on which the 433 MHz receiver module is connected. | + # | | | 0 -> D2 / 1 -> D3 | + # | +---------+-----------------------------------------------------------+ + # | | 1 - 3 | Subscription number | + # | | | | + # | +---------+-----------------------------------------------------------+ + # | | 4 | Unused | + # | | | | + # | +---------+-----------------------------------------------------------+ + # | | 5 | Initial state. | + # | | | 0 = off / 1 = on | + # | +---------+-----------------------------------------------------------+ + # | | 6 - 7 | RC switch type. | + # | | | Type D: 11 | + # +---------+---------+-----------------------------------------------------------+ + # | 3 | 0 - 7 | Device family ('A' - 'D'), as the ASCII code of the | + # | | | desired letter. | + # | | | Ex: 'C' --> 0100 0011 | + # +---------+---------+-----------------------------------------------------------+ + # | 4 | 0 - 7 | Device id (1 - 3). | + # | | | Ex: 3 --> 0000 0011 | + # +---------+---------+-----------------------------------------------------------+ + + grovepi.write_i2c_block(grovepi.address, + grovepi.rcswitch_sub_cmd + + [self.TYPE_D * 64 + initial_state * 32 + subscription_number * 2 + self.rx_pin - 2, + ord(group), + device]) + + def read_subscription(self, subscription_number): + """Read last command received for a given subscription number + + :param subscription_number: + :return: STATE_ON, STATE_OFF or STATE_UNKNOWN + """ + + # +---------+---------+-----------------------------------------------------------+ + # | Byte | Bits | Description | + # +---------+---------+-----------------------------------------------------------+ + # | 1 | 0 - 7 | Command byte: subscribe to RC switch commands. | + # | | | Decimal value: 112 | + # +---------+---------+-----------------------------------------------------------+ + # | 2 | 0 | Unused | + # | | | | + # | +---------+-----------------------------------------------------------+ + # | | 1 - 3 | Subscription number | + # | | | | + # | +---------+-----------------------------------------------------------+ + # | | 4 - 7 | Unused | + # | | | | + # +---------+---------+-----------------------------------------------------------+ + # | 3 | 0 - 7 | Unused | + # | | | | + # +---------+---------+-----------------------------------------------------------+ + # | 4 | 0 - 7 | Unused | + # | | | | + # +---------+---------+-----------------------------------------------------------+ + grovepi.write_i2c_block(grovepi.address, + grovepi.rcswitch_read_cmd + # Byte 1 + [subscription_number * 2, # Byte 2 + 0, # Byte 3 + 0]) # Byte 4 + + return grovepi.read_i2c_byte(grovepi.address) \ No newline at end of file diff --git a/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch_example.py b/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch_example.py new file mode 100644 index 00000000..0eeded6e --- /dev/null +++ b/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch_example.py @@ -0,0 +1,19 @@ +import grove_433mhz_rx_rcswitch as rcswitch +import time + +# Transmitter module should be connected to grovepi socket D2 +rx_pin = 2 + +rc = rcswitch.Grove433mhzRxRCSwitch(rx_pin) + + +# Subscribe to commands sent to a type B remote, with a group 2, device 3 settings. +# Subscription uses slot #1. +rc.subscribe_type_b(1, rc.STATE_OFF, 2, 3) + +time.sleep(1); + +# Continuously show the last received command +while 1: + print rc.read_subscription(1) + time.sleep(3) \ No newline at end of file diff --git a/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_tx_rcswitch.py b/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_tx_rcswitch.py new file mode 100644 index 00000000..6794e8af --- /dev/null +++ b/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_tx_rcswitch.py @@ -0,0 +1,220 @@ +import grovepi + + +class Grove433mhzTxRCSwitch: + """ Sends command codes to radio remote controlled switches or devices. + + This library sends on / off commands to 4 different types of RC devices. Each of these types can be identified by + the way the device address is configured: + - Type A devices addresses are set by 5 DIP switches + - Type B devices addresses are set by 2 rotary or sliding switches, each with 4 positions + - Type C 'Intertechno' devices addresses are set with 3 elements: family (a to f), group (1 to 4) and device + (1 to 4) + - Type D 'REV' devices addresses are set with 2 elements: group (A to D) and device (1 to 3) + + This library makes use of the RCSwitch library: https://github.com/sui77/rc-switch/ + + This method does not update existing subscriptions defined using grove_433mhz_rx_rcswitch + + """ + + # Remote-controlled switch types + TYPE_A = 0 + TYPE_B = 1 + TYPE_C = 2 + TYPE_D = 3 + + # Remote-controlled switch state + COMMAND_ON = 1 + COMMAND_OFF = 0 + + def __init__(self, tx_pin): + """Sets the Digital pin on which the TX component is wired. + + :param tx_pin: For D2, set to 2. Only pins D2 and D3 are compatible with this grove component. + """ + self.tx_pin = tx_pin + + def send_type_a(self, command, group, device): + """Sends an ON or OFF command to a type A device. + + Type A devices addresses are typically set by 5 DIP switches. + + :param command: set to COMMAND_ON or COMMAND_OFF. + :param group: group DIP switch setting, as a string of five 0s/1s. Set to the '00101' string for a DIP switch + set to OFF OFF ON OFF ON. + :param device: device DIP switch setting, as a string of five 0s/1s. Set to the '00101' string for a DIP switch + set to OFF OFF ON OFF ON. + """ + + # Send command to the firmware, using the following scheme: + # +-------------------------------------------------------------------------------+ + # | Type A switch | + # +---------+---------+-----------------------------------------------------------+ + # | Byte | Bits | Description | + # +---------+---------+-----------------------------------------------------------+ + # | 1 | 0 - 7 | Command byte: send command to RC switch. | + # | | | Decimal value: 110 | + # +---------+---------+-----------------------------------------------------------+ + # | 2 | 0 | Pin on which the 433 MHz transmitter module is connected. | + # | | | 0 -> D2 / 1 -> D3 | + # | +---------+-----------------------------------------------------------+ + # | | 1 - 4 | Unused | + # | | | | + # | +---------+-----------------------------------------------------------+ + # | | 5 | Requested switch state. | + # | | | 0 = off / 1 = on | + # | +---------+-----------------------------------------------------------+ + # | | 6 - 7 | RC switch type. | + # | | | Type A: 00 | + # +---------+---------+-----------------------------------------------------------+ + # | 3 | 0 - 4 | Group DIP switch. | + # | | | Ex: OFF-ON-ON-ON-OFF --> 01110 | + # | +---------+-----------------------------------------------------------+ + # | | 5 - 7 | Unused | + # | | | | + # +---------+---------+-----------------------------------------------------------+ + # | 4 | 0 - 4 | Device DIP switch. | + # | | | Ex: OFF-ON-ON-ON-OFF --> 01110 | + # | +---------+-----------------------------------------------------------+ + # | | 5 - 7 | Unused | + # | | | | + # +---------+---------+-----------------------------------------------------------+ + grovepi.write_i2c_block(grovepi.address, + grovepi.rcswitch_send_cmd + # Byte 1 + [self.TYPE_A * 64 + command * 32 + self.tx_pin - 2, # Byte 2 + int(group, 2), # Byte 3 + int(device, 2)]) # Byte 4 + + + def send_type_b(self, command, group, device): + """Sends an ON or OFF command to a type B device. + + Type B devices addresses are typically set by two rotary or sliding switches, numbered from 1 to 4. + + :param command: set to COMMAND_ON or COMMAND_OFF. + :param group: group switch setting (1 to 4). + :param device: device switch setting (1 to 4). + """ + + # Send command to the firmware, using the following scheme: + # +-------------------------------------------------------------------------------+ + # | Type B switch | + # +---------+---------+-----------------------------------------------------------+ + # | Byte | Bits | Description | + # +---------+---------+-----------------------------------------------------------+ + # | 1 | 0 - 7 | Command byte: send command to RC switch. | + # | | | Decimal value: 110 | + # +---------+---------+-----------------------------------------------------------+ + # | 2 | 0 | Pin on which the 433 MHz transmitter module is connected. | + # | | | 0 -> D2 / 1 -> D3 | + # | +---------+-----------------------------------------------------------+ + # | | 1 - 4 | Unused | + # | | | | + # | +---------+-----------------------------------------------------------+ + # | | 5 | Requested switch state. | + # | | | 0 = off / 1 = on | + # | +---------+-----------------------------------------------------------+ + # | | 6 - 7 | RC switch type. | + # | | | Type B: 01 | + # +---------+---------+-----------------------------------------------------------+ + # | 3 | 0 - 7 | Group id (1 - 4). | + # | | | Ex: 3 --> 0000 0011 | + # +---------+---------+-----------------------------------------------------------+ + # | 4 | 0 - 7 | Device id (1 - 4). | + # | | | Ex: 3 --> 0000 0011 | + # +---------+---------+-----------------------------------------------------------+ + grovepi.write_i2c_block(grovepi.address, + grovepi.rcswitch_send_cmd + + [self.TYPE_B * 64 + command * 32 + self.tx_pin - 2, + group, + device]) + + def send_type_c(self, command, family, group, device): + """Sends an ON or OFF command to a type C (Intertechno) device. + + :param command: set to COMMAND_ON or COMMAND_OFF. + :param family: device family setting ('a' to 'f') + :param group: device group (1 to 4). + :param device: device id (1 to 4). + """ + + # +-------------------------------------------------------------------------------+ + # | Type C switch | + # +---------+---------+-----------------------------------------------------------+ + # | Byte | Bits | Description | + # +---------+---------+-----------------------------------------------------------+ + # | 1 | 0 - 7 | Command byte: send command to RC switch. | + # | | | Decimal value: 110 | + # +---------+---------+-----------------------------------------------------------+ + # | 2 | 0 | Pin on which the 433 MHz transmitter module is connected. | + # | | | 0 -> D2 / 1 -> D3 | + # | +---------+-----------------------------------------------------------+ + # | | 1 - 4 | Unused | + # | | | | + # | +---------+-----------------------------------------------------------+ + # | | 5 | Requested switch state. | + # | | | 0 = off / 1 = on | + # | +---------+-----------------------------------------------------------+ + # | | 6 - 7 | RC switch type. | + # | | | Type C: 10 | + # +---------+---------+-----------------------------------------------------------+ + # | 3 | 0 - 7 | Device family ('a' - 'f'), as the ASCII code of the | + # | | | desired letter. | + # | | | Ex: 'b' --> 0110 0001 | + # +---------+---------+-----------------------------------------------------------+ + # | 4 | 0 - 1 | Device id (1 - 4), minus 1. | + # | | | Ex: device #3 --> 10 | + # | +---------+-----------------------------------------------------------+ + # | | 2 - 3 | Device group (1 - 4), minus 1. | + # | | | Ex: group #1 --> 00 | + # + +---------+-----------------------------------------------------------+ + # | | 4 - 7 | Unused | + # | | | | + # +---------+---------+-----------------------------------------------------------+ + grovepi.write_i2c_block(grovepi.address, + grovepi.rcswitch_send_cmd + + [self.TYPE_C * 64 + command * 32 + self.tx_pin - 2, + ord(family), + (group - 1) * 4 + (device - 1)]) + + def send_type_d(self, command, family, device): + """Sends an ON or OFF command to a type D device. + + :param command: set to COMMAND_ON or COMMAND_OFF. + :param family: device family setting ('A' to 'D') + :param device: device id (1 to 4). + """ + + # +-------------------------------------------------------------------------------+ + # | Type D switch | + # +---------+---------+-----------------------------------------------------------+ + # | Byte | Bits | Description | + # +---------+---------+-----------------------------------------------------------+ + # | 1 | 0 - 7 | Command byte: send command to RC switch. | + # | | | Decimal value: 110 | + # +---------+---------+-----------------------------------------------------------+ + # | 2 | 0 | Pin on which the 433 MHz transmitter module is connected. | + # | | | 0 -> D2 / 1 -> D3 | + # | +---------+-----------------------------------------------------------+ + # | | 1 - 4 | Unused | + # | | | | + # | +---------+-----------------------------------------------------------+ + # | | 5 | Requested switch state. | + # | | | 0 = off / 1 = on | + # | +---------+-----------------------------------------------------------+ + # | | 6 - 7 | RC switch type. | + # | | | Type D: 11 | + # +---------+---------+-----------------------------------------------------------+ + # | 3 | 0 - 7 | Device family ('A' - 'D'), as the ASCII code of the | + # | | | desired letter. | + # | | | Ex: 'C' --> 0100 0011 | + # +---------+---------+-----------------------------------------------------------+ + # | 4 | 0 - 7 | Device id (1 - 3). | + # | | | Ex: 3 --> 0000 0011 | + # +---------+---------+-----------------------------------------------------------+ + grovepi.write_i2c_block(grovepi.address, + grovepi.rcswitch_send_cmd + + [self.TYPE_D * 64 + command * 32 + self.tx_pin - 2, + ord(family), + device]) diff --git a/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_tx_rcswitch_example.py b/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_tx_rcswitch_example.py new file mode 100644 index 00000000..36355149 --- /dev/null +++ b/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_tx_rcswitch_example.py @@ -0,0 +1,59 @@ +import grove_433mhz_tx_rcswitch as rcswitch +import time + +# Transmitter module should be connected to grovepi socket D3 +tx_pin = 3 + +rc = rcswitch.Grove433mhzTxRCSwitch(tx_pin) + +# Example 1: remote control of a type A switch, with a family DIP selector set to "ON ON ON OFF OFF" and a device +# DIP selector set to "OFF ON OFF ON OFF". + +# Turn on the switch +rc.send_type_a(rc.COMMAND_ON, "11100", "01010") + +time.sleep(3) + +# Turn off the switch +rc.send_type_a(rc.COMMAND_OFF, "11100", "01010") + +time.sleep(3) + + +# Example 2: remote control of a type B switch, with two rotary/sliding switchs. Group switch set to II and device +# switch set to 3. + +# Turn on the switch +rc.send_type_b(rc.COMMAND_ON, 2, 3) + +time.sleep(3) + +# Turn off the switch +rc.send_type_b(rc.COMMAND_OFF, 2, 3) + +time.sleep(3) + + +# Example 3: remote control of a type C switch (Intertechno), with a family code of c, a group set to 3 and a device set +# to 1. + +# Turn on the switch +rc.send_type_c(rc.COMMAND_ON, "c", 3, 1) + +time.sleep(3) + +# Turn off the switch +rc.send_type_c(rc.COMMAND_OFF, "c", 3, 1) + +time.sleep(3) + + +# Example 4: remote control of a type D switch, with a group set to D and a device set to 3 + +# Turn on the switch +rc.send_type_d(rc.COMMAND_ON, "D", 3) + +time.sleep(3) + +# Turn off the switch +rc.send_type_d(rc.COMMAND_OFF, "D", 3) diff --git a/Software/Python/grovepi.py b/Software/Python/grovepi.py index 020b0e5d..c36abafd 100755 --- a/Software/Python/grovepi.py +++ b/Software/Python/grovepi.py @@ -158,6 +158,11 @@ flow_read_cmd=[12] flow_disable_cmd=[13] flow_en_cmd=[18] + +rcswitch_send_cmd=[110] +rcswitch_sub_cmd=[111] +rcswitch_read_cmd=[112] + # This allows us to be more specific about which commands contain unused bytes unused = 0 retries = 10 diff --git a/Software/Python/test.py b/Software/Python/test.py new file mode 100644 index 00000000..d0912864 --- /dev/null +++ b/Software/Python/test.py @@ -0,0 +1,16 @@ +import grovepi + + +TYPE_A = 0 +TYPE_B = 1 +TYPE_C = 2 +TYPE_D = 3 + +COMMAND_ON = 1 +COMMAND_OFF = 0 +tx_pin = 5 +rx_pin = 6 + +# Allumage lampe +grovepi.write_i2c_block(grovepi.address, 110 + [TYPE_B * 64 + COMMAND_ON * 32 + tx_pin, 2, 1]) + From 35f462ef5319c145372f721f5c7738fd86f61a3e Mon Sep 17 00:00:00 2001 From: Nikkoura Date: Mon, 22 May 2017 23:20:28 +0200 Subject: [PATCH 2/3] Revert unwanted changes --- .gitignore | 9 +-------- Software/Python/test.py | 16 ---------------- 2 files changed, 1 insertion(+), 24 deletions(-) delete mode 100644 Software/Python/test.py diff --git a/.gitignore b/.gitignore index 64dbe123..af8a65a8 100644 --- a/.gitignore +++ b/.gitignore @@ -50,11 +50,4 @@ Release # Visual Studio files *.pyc -*.pfx - -# Jetbrains files -.idea - -# CMake files -cmake*/ -CMakeLists.txt +*.pfx \ No newline at end of file diff --git a/Software/Python/test.py b/Software/Python/test.py deleted file mode 100644 index d0912864..00000000 --- a/Software/Python/test.py +++ /dev/null @@ -1,16 +0,0 @@ -import grovepi - - -TYPE_A = 0 -TYPE_B = 1 -TYPE_C = 2 -TYPE_D = 3 - -COMMAND_ON = 1 -COMMAND_OFF = 0 -tx_pin = 5 -rx_pin = 6 - -# Allumage lampe -grovepi.write_i2c_block(grovepi.address, 110 + [TYPE_B * 64 + COMMAND_ON * 32 + tx_pin, 2, 1]) - From d473de59d3923866f817883452386365b731e7af Mon Sep 17 00:00:00 2001 From: Nikkoura Date: Thu, 25 May 2017 00:05:11 +0200 Subject: [PATCH 3/3] Python library for RF Link 433MHz / RCSwitch: correct bug with 'initial_state' subscription parameter --- .../grove_433mhz_rx_rcswitch.py | 8 ++++---- .../grove_433mhz_rx_rcswitch_example.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch.py b/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch.py index b2be7d7e..cc98bb54 100644 --- a/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch.py +++ b/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch.py @@ -41,7 +41,7 @@ def __init__(self, rx_pin): """ self.rx_pin = rx_pin - def subscribe_type_a(self, subscription_number, group, device, initial_state=STATE_UNKNOWN): + def subscribe_type_a(self, subscription_number, group, device, initial_state): """Subscribe to commands normally sent to a type A device. Type A devices addresses are typically set by 5 DIP switches. @@ -99,7 +99,7 @@ def subscribe_type_a(self, subscription_number, group, device, initial_state=STA int(group, 2), int(device, 2)]) - def subscribe_type_b(self, subscription_number, group, device, initial_state=STATE_UNKNOWN): + def subscribe_type_b(self, subscription_number, group, device, initial_state): """Subscribe to commands normally sent to a type B device. Type B devices addresses are typically set by two rotary or sliding switches, numbered from 1 to 4. @@ -152,7 +152,7 @@ def subscribe_type_b(self, subscription_number, group, device, initial_state=STA group, device]) - def subscribe_type_c(self, subscription_number, family, group, device, initial_state=STATE_UNKNOWN): + def subscribe_type_c(self, subscription_number, family, group, device, initial_state): """Subscribe to commands normally sent to a type C device. :param subscription_number: subscription slot to use. Can be any value from 0 to 7. @@ -212,7 +212,7 @@ def subscribe_type_c(self, subscription_number, family, group, device, initial_s ord(family), (group - 1) * 4 + (device - 1)]) - def subscribe_type_d(self, subscription_number, group, device, initial_state=STATE_UNKNOWN): + def subscribe_type_d(self, subscription_number, group, device, initial_state): """Subscribe to commands normally sent to a type D device. :param subscription_number: subscription slot to use. Can be any value from 0 to 7. diff --git a/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch_example.py b/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch_example.py index 0eeded6e..91c4c524 100644 --- a/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch_example.py +++ b/Software/Python/grove_rflink433mhz_rcswitch/grove_433mhz_rx_rcswitch_example.py @@ -9,7 +9,7 @@ # Subscribe to commands sent to a type B remote, with a group 2, device 3 settings. # Subscription uses slot #1. -rc.subscribe_type_b(1, rc.STATE_OFF, 2, 3) +rc.subscribe_type_b(1, 2, 3, rc.STATE_OFF) time.sleep(1);